Skip to content

Commit 5364910

Browse files
committed
feat: use session token for API auth
Signed-off-by: Aurora Gaffney <aurora@blinklabs.io>
1 parent a42b89d commit 5364910

12 files changed

Lines changed: 1133 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: 121 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
],
@@ -169,6 +235,11 @@ const docTemplate = `{
169235
},
170236
"/api/client/wg-devices": {
171237
"post": {
238+
"security": [
239+
{
240+
"BearerAuth": []
241+
}
242+
],
172243
"description": "List all WireGuard devices registered for a client",
173244
"consumes": [
174245
"application/json"
@@ -224,6 +295,11 @@ const docTemplate = `{
224295
},
225296
"/api/client/wg-peer": {
226297
"delete": {
298+
"security": [
299+
{
300+
"BearerAuth": []
301+
}
302+
],
227303
"description": "Remove a WireGuard device registration",
228304
"consumes": [
229305
"application/json"
@@ -291,6 +367,11 @@ const docTemplate = `{
291367
},
292368
"/api/client/wg-profile": {
293369
"post": {
370+
"security": [
371+
{
372+
"BearerAuth": []
373+
}
374+
],
294375
"description": "Get a WireGuard configuration profile for a registered device",
295376
"consumes": [
296377
"application/json"
@@ -359,6 +440,11 @@ const docTemplate = `{
359440
},
360441
"/api/client/wg-register": {
361442
"post": {
443+
"security": [
444+
{
445+
"BearerAuth": []
446+
}
447+
],
362448
"description": "Register a new WireGuard device for a client",
363449
"consumes": [
364450
"application/json"
@@ -690,12 +776,6 @@ const docTemplate = `{
690776
"properties": {
691777
"id": {
692778
"type": "string"
693-
},
694-
"key": {
695-
"type": "string"
696-
},
697-
"signature": {
698-
"type": "string"
699779
}
700780
}
701781
},
@@ -738,6 +818,32 @@ const docTemplate = `{
738818
}
739819
}
740820
},
821+
"api.SessionRequest": {
822+
"type": "object",
823+
"required": [
824+
"key",
825+
"signature"
826+
],
827+
"properties": {
828+
"key": {
829+
"type": "string"
830+
},
831+
"signature": {
832+
"type": "string"
833+
}
834+
}
835+
},
836+
"api.SessionResponse": {
837+
"type": "object",
838+
"properties": {
839+
"expires_at": {
840+
"type": "integer"
841+
},
842+
"token": {
843+
"type": "string"
844+
}
845+
}
846+
},
741847
"api.TxRenewRequest": {
742848
"type": "object",
743849
"properties": {
@@ -825,12 +931,6 @@ const docTemplate = `{
825931
"client_id": {
826932
"type": "string"
827933
},
828-
"key": {
829-
"type": "string"
830-
},
831-
"signature": {
832-
"type": "string"
833-
},
834934
"wg_pubkey": {
835935
"type": "string"
836936
}
@@ -866,12 +966,6 @@ const docTemplate = `{
866966
"properties": {
867967
"client_id": {
868968
"type": "string"
869-
},
870-
"key": {
871-
"type": "string"
872-
},
873-
"signature": {
874-
"type": "string"
875969
}
876970
}
877971
},
@@ -895,12 +989,6 @@ const docTemplate = `{
895989
"client_id": {
896990
"type": "string"
897991
},
898-
"key": {
899-
"type": "string"
900-
},
901-
"signature": {
902-
"type": "string"
903-
},
904992
"wg_pubkey": {
905993
"type": "string"
906994
}
@@ -912,12 +1000,6 @@ const docTemplate = `{
9121000
"client_id": {
9131001
"type": "string"
9141002
},
915-
"key": {
916-
"type": "string"
917-
},
918-
"signature": {
919-
"type": "string"
920-
},
9211003
"wg_pubkey": {
9221004
"type": "string"
9231005
}
@@ -940,6 +1022,14 @@ const docTemplate = `{
9401022
}
9411023
}
9421024
}
1025+
},
1026+
"securityDefinitions": {
1027+
"BearerAuth": {
1028+
"description": "Session token from POST /api/auth/session, sent as \"Bearer \u003ctoken\u003e\".",
1029+
"type": "apiKey",
1030+
"name": "Authorization",
1031+
"in": "header"
1032+
}
9431033
}
9441034
}`
9451035

0 commit comments

Comments
 (0)