Skip to content

Commit 85463d4

Browse files
authored
feat: use session token for API auth (#361)
Signed-off-by: Aurora Gaffney <aurora@blinklabs.io>
1 parent ae77e4d commit 85463d4

12 files changed

Lines changed: 1229 additions & 472 deletions

File tree

cmd/vpn-indexer/main.go

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,19 @@ func main() {
157157
var crlInstance *crl.Crl
158158
var wgClient *wireguard.Client
159159
var s3Client *client.Client
160+
var jwtIssuer *jwt.Issuer
161+
162+
// Initialize the JWT issuer up front: it signs browser session tokens for
163+
// every protocol (so /api/auth/session is available regardless of protocol)
164+
// and also authenticates the indexer to the WireGuard container. The key
165+
// file config (VPN_JWT_KEY_FILE) is required for all protocols.
166+
jwtIssuer, err = jwt.NewIssuer(cfg.Vpn.JWTKeyFile)
167+
if err != nil {
168+
slog.Error(
169+
fmt.Sprintf("failed to initialize JWT issuer: %s", err),
170+
)
171+
os.Exit(1)
172+
}
160173

161174
switch cfg.Vpn.Protocol {
162175
case "openvpn":
@@ -181,15 +194,6 @@ func main() {
181194
// Initialize WireGuard components if protocol is wireguard
182195
slog.Info("initializing WireGuard components")
183196

184-
// Initialize JWT issuer for WG container authentication
185-
jwtIssuer, err := jwt.NewIssuer(cfg.Vpn.WGJWTKeyFile)
186-
if err != nil {
187-
slog.Error(
188-
fmt.Sprintf("failed to initialize JWT issuer: %s", err),
189-
)
190-
os.Exit(1)
191-
}
192-
193197
// Initialize WG container client
194198
wgClient = wireguard.NewClient(cfg.Vpn.WGContainerURL, jwtIssuer)
195199

@@ -252,7 +256,7 @@ func main() {
252256
}
253257

254258
// Start API listener
255-
if err := api.Start(cfg, db, caInstance, wgClient, s3Client); err != nil {
259+
if err := api.Start(cfg, db, caInstance, wgClient, s3Client, jwtIssuer); err != nil {
256260
slog.Error(
257261
"failed to start API:",
258262
"error",

docs/docs.go

Lines changed: 133 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,67 @@ const docTemplate = `{
2323
"host": "{{.Host}}",
2424
"basePath": "{{.BasePath}}",
2525
"paths": {
26+
"/api/auth/session": {
27+
"post": {
28+
"description": "Exchange a wallet-signed challenge for a short-lived session token covering all of the wallet's subscriptions",
29+
"consumes": [
30+
"application/json"
31+
],
32+
"produces": [
33+
"application/json"
34+
],
35+
"summary": "AuthSession",
36+
"parameters": [
37+
{
38+
"description": "Session Request",
39+
"name": "SessionRequest",
40+
"in": "body",
41+
"required": true,
42+
"schema": {
43+
"$ref": "#/definitions/api.SessionRequest"
44+
}
45+
}
46+
],
47+
"responses": {
48+
"200": {
49+
"description": "Session token",
50+
"schema": {
51+
"$ref": "#/definitions/api.SessionResponse"
52+
}
53+
},
54+
"400": {
55+
"description": "Bad Request",
56+
"schema": {
57+
"$ref": "#/definitions/api.ErrorResponse"
58+
}
59+
},
60+
"401": {
61+
"description": "Unauthorized",
62+
"schema": {
63+
"$ref": "#/definitions/api.ErrorResponse"
64+
}
65+
},
66+
"403": {
67+
"description": "Forbidden (no subscriptions for wallet)",
68+
"schema": {
69+
"$ref": "#/definitions/api.ErrorResponse"
70+
}
71+
},
72+
"405": {
73+
"description": "Method Not Allowed",
74+
"schema": {
75+
"type": "string"
76+
}
77+
},
78+
"500": {
79+
"description": "Server Error",
80+
"schema": {
81+
"$ref": "#/definitions/api.ErrorResponse"
82+
}
83+
}
84+
}
85+
}
86+
},
2687
"/api/client/available": {
2788
"post": {
2889
"description": "Check if a client profile is available",
@@ -123,7 +184,12 @@ const docTemplate = `{
123184
},
124185
"/api/client/profile": {
125186
"post": {
126-
"description": "Fetch a client VPN profile given a COSE payload via signed S3 link",
187+
"security": [
188+
{
189+
"BearerAuth": []
190+
}
191+
],
192+
"description": "Fetch a client VPN profile via a signed S3 link; authenticate with a session Bearer token and provide the subscription id in the body",
127193
"consumes": [
128194
"application/json"
129195
],
@@ -152,6 +218,18 @@ const docTemplate = `{
152218
"type": "string"
153219
}
154220
},
221+
"401": {
222+
"description": "Unauthorized",
223+
"schema": {
224+
"type": "string"
225+
}
226+
},
227+
"403": {
228+
"description": "Forbidden",
229+
"schema": {
230+
"type": "string"
231+
}
232+
},
155233
"405": {
156234
"description": "Method Not Allowed",
157235
"schema": {
@@ -169,6 +247,11 @@ const docTemplate = `{
169247
},
170248
"/api/client/wg-devices": {
171249
"post": {
250+
"security": [
251+
{
252+
"BearerAuth": []
253+
}
254+
],
172255
"description": "List all WireGuard devices registered for a client",
173256
"consumes": [
174257
"application/json"
@@ -224,6 +307,11 @@ const docTemplate = `{
224307
},
225308
"/api/client/wg-peer": {
226309
"delete": {
310+
"security": [
311+
{
312+
"BearerAuth": []
313+
}
314+
],
227315
"description": "Remove a WireGuard device registration",
228316
"consumes": [
229317
"application/json"
@@ -291,6 +379,11 @@ const docTemplate = `{
291379
},
292380
"/api/client/wg-profile": {
293381
"post": {
382+
"security": [
383+
{
384+
"BearerAuth": []
385+
}
386+
],
294387
"description": "Get a WireGuard configuration profile for a registered device",
295388
"consumes": [
296389
"application/json"
@@ -359,6 +452,11 @@ const docTemplate = `{
359452
},
360453
"/api/client/wg-register": {
361454
"post": {
455+
"security": [
456+
{
457+
"BearerAuth": []
458+
}
459+
],
362460
"description": "Register a new WireGuard device for a client",
363461
"consumes": [
364462
"application/json"
@@ -690,12 +788,6 @@ const docTemplate = `{
690788
"properties": {
691789
"id": {
692790
"type": "string"
693-
},
694-
"key": {
695-
"type": "string"
696-
},
697-
"signature": {
698-
"type": "string"
699791
}
700792
}
701793
},
@@ -738,6 +830,32 @@ const docTemplate = `{
738830
}
739831
}
740832
},
833+
"api.SessionRequest": {
834+
"type": "object",
835+
"required": [
836+
"key",
837+
"signature"
838+
],
839+
"properties": {
840+
"key": {
841+
"type": "string"
842+
},
843+
"signature": {
844+
"type": "string"
845+
}
846+
}
847+
},
848+
"api.SessionResponse": {
849+
"type": "object",
850+
"properties": {
851+
"expires_at": {
852+
"type": "integer"
853+
},
854+
"token": {
855+
"type": "string"
856+
}
857+
}
858+
},
741859
"api.TxRenewRequest": {
742860
"type": "object",
743861
"properties": {
@@ -825,12 +943,6 @@ const docTemplate = `{
825943
"client_id": {
826944
"type": "string"
827945
},
828-
"key": {
829-
"type": "string"
830-
},
831-
"signature": {
832-
"type": "string"
833-
},
834946
"wg_pubkey": {
835947
"type": "string"
836948
}
@@ -866,12 +978,6 @@ const docTemplate = `{
866978
"properties": {
867979
"client_id": {
868980
"type": "string"
869-
},
870-
"key": {
871-
"type": "string"
872-
},
873-
"signature": {
874-
"type": "string"
875981
}
876982
}
877983
},
@@ -895,12 +1001,6 @@ const docTemplate = `{
8951001
"client_id": {
8961002
"type": "string"
8971003
},
898-
"key": {
899-
"type": "string"
900-
},
901-
"signature": {
902-
"type": "string"
903-
},
9041004
"wg_pubkey": {
9051005
"type": "string"
9061006
}
@@ -912,12 +1012,6 @@ const docTemplate = `{
9121012
"client_id": {
9131013
"type": "string"
9141014
},
915-
"key": {
916-
"type": "string"
917-
},
918-
"signature": {
919-
"type": "string"
920-
},
9211015
"wg_pubkey": {
9221016
"type": "string"
9231017
}
@@ -940,6 +1034,14 @@ const docTemplate = `{
9401034
}
9411035
}
9421036
}
1037+
},
1038+
"securityDefinitions": {
1039+
"BearerAuth": {
1040+
"description": "Session token from POST /api/auth/session, sent as \"Bearer \u003ctoken\u003e\".",
1041+
"type": "apiKey",
1042+
"name": "Authorization",
1043+
"in": "header"
1044+
}
9431045
}
9441046
}`
9451047

0 commit comments

Comments
 (0)