@fleetx_io/fleetx-mcp-server 1.1.7 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -12
- package/README.md +56 -6
- package/dist/auth/fleetx/exchange.d.ts +13 -0
- package/dist/auth/fleetx/exchange.d.ts.map +1 -0
- package/dist/auth/fleetx/exchange.js +27 -0
- package/dist/auth/fleetx/exchange.js.map +1 -0
- package/dist/auth/google/callback.d.ts +3 -0
- package/dist/auth/google/callback.d.ts.map +1 -0
- package/dist/auth/google/callback.js +98 -0
- package/dist/auth/google/callback.js.map +1 -0
- package/dist/auth/google/client.d.ts +17 -0
- package/dist/auth/google/client.d.ts.map +1 -0
- package/dist/auth/google/client.js +50 -0
- package/dist/auth/google/client.js.map +1 -0
- package/dist/auth/oauth/authorize.d.ts +3 -0
- package/dist/auth/oauth/authorize.d.ts.map +1 -0
- package/dist/auth/oauth/authorize.js +74 -0
- package/dist/auth/oauth/authorize.js.map +1 -0
- package/dist/auth/oauth/cimd.d.ts +33 -0
- package/dist/auth/oauth/cimd.d.ts.map +1 -0
- package/dist/auth/oauth/cimd.js +63 -0
- package/dist/auth/oauth/cimd.js.map +1 -0
- package/dist/auth/oauth/pending-sessions.d.ts +5 -0
- package/dist/auth/oauth/pending-sessions.d.ts.map +1 -0
- package/dist/auth/oauth/pending-sessions.js +24 -0
- package/dist/auth/oauth/pending-sessions.js.map +1 -0
- package/dist/auth/oauth/pkce.d.ts +3 -0
- package/dist/auth/oauth/pkce.d.ts.map +1 -0
- package/dist/auth/oauth/pkce.js +7 -0
- package/dist/auth/oauth/pkce.js.map +1 -0
- package/dist/auth/oauth/register.d.ts +8 -0
- package/dist/auth/oauth/register.d.ts.map +1 -0
- package/dist/auth/oauth/register.js +37 -0
- package/dist/auth/oauth/register.js.map +1 -0
- package/dist/auth/oauth/token.d.ts +3 -0
- package/dist/auth/oauth/token.d.ts.map +1 -0
- package/dist/auth/oauth/token.js +111 -0
- package/dist/auth/oauth/token.js.map +1 -0
- package/dist/auth/oauth/types.d.ts +11 -0
- package/dist/auth/oauth/types.d.ts.map +1 -0
- package/dist/auth/oauth/types.js +2 -0
- package/dist/auth/oauth/types.js.map +1 -0
- package/dist/auth/oauth/userinfo.d.ts +3 -0
- package/dist/auth/oauth/userinfo.d.ts.map +1 -0
- package/dist/auth/oauth/userinfo.js +29 -0
- package/dist/auth/oauth/userinfo.js.map +1 -0
- package/dist/auth/storage/authorization-code.d.ts +19 -0
- package/dist/auth/storage/authorization-code.d.ts.map +1 -0
- package/dist/auth/storage/authorization-code.js +44 -0
- package/dist/auth/storage/authorization-code.js.map +1 -0
- package/dist/auth/storage/connected-account.d.ts +24 -0
- package/dist/auth/storage/connected-account.d.ts.map +1 -0
- package/dist/auth/storage/connected-account.js +60 -0
- package/dist/auth/storage/connected-account.js.map +1 -0
- package/dist/auth/storage/db.d.ts +3 -0
- package/dist/auth/storage/db.d.ts.map +1 -0
- package/dist/auth/storage/db.js +110 -0
- package/dist/auth/storage/db.js.map +1 -0
- package/dist/auth/storage/oauth-client.d.ts +12 -0
- package/dist/auth/storage/oauth-client.d.ts.map +1 -0
- package/dist/auth/storage/oauth-client.js +28 -0
- package/dist/auth/storage/oauth-client.js.map +1 -0
- package/dist/auth/storage/refresh-token.d.ts +17 -0
- package/dist/auth/storage/refresh-token.d.ts.map +1 -0
- package/dist/auth/storage/refresh-token.js +46 -0
- package/dist/auth/storage/refresh-token.js.map +1 -0
- package/dist/auth/tokens/encryption.d.ts +5 -0
- package/dist/auth/tokens/encryption.d.ts.map +1 -0
- package/dist/auth/tokens/encryption.js +27 -0
- package/dist/auth/tokens/encryption.js.map +1 -0
- package/dist/auth/tokens/jwt.d.ts +8 -0
- package/dist/auth/tokens/jwt.d.ts.map +1 -0
- package/dist/auth/tokens/jwt.js +34 -0
- package/dist/auth/tokens/jwt.js.map +1 -0
- package/dist/auth.d.ts +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +2 -1
- package/dist/auth.js.map +1 -1
- package/dist/config/oauth.d.ts +21 -0
- package/dist/config/oauth.d.ts.map +1 -0
- package/dist/config/oauth.js +41 -0
- package/dist/config/oauth.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp/middleware/auth.d.ts +18 -0
- package/dist/mcp/middleware/auth.d.ts.map +1 -0
- package/dist/mcp/middleware/auth.js +35 -0
- package/dist/mcp/middleware/auth.js.map +1 -0
- package/dist/server/createMcpServer.d.ts +8 -3
- package/dist/server/createMcpServer.d.ts.map +1 -1
- package/dist/server/createMcpServer.js +28 -12
- package/dist/server/createMcpServer.js.map +1 -1
- package/dist/transports/fastify.d.ts +2 -0
- package/dist/transports/fastify.d.ts.map +1 -0
- package/dist/transports/fastify.js +223 -0
- package/dist/transports/fastify.js.map +1 -0
- package/dist/transports/sse.d.ts +1 -1
- package/dist/transports/sse.d.ts.map +1 -1
- package/dist/transports/sse.js +162 -67
- package/dist/transports/sse.js.map +1 -1
- package/dist/transports/stdio.d.ts.map +1 -1
- package/dist/transports/stdio.js +8 -1
- package/dist/transports/stdio.js.map +1 -1
- package/dist/utils-http.d.ts +8 -0
- package/dist/utils-http.d.ts.map +1 -0
- package/dist/utils-http.js +15 -0
- package/dist/utils-http.js.map +1 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +12 -2
- package/dist/utils.js.map +1 -1
- package/dist/well-known/discovery.d.ts +4 -0
- package/dist/well-known/discovery.d.ts.map +1 -0
- package/dist/well-known/discovery.js +25 -0
- package/dist/well-known/discovery.js.map +1 -0
- package/package.json +15 -7
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/pkce.ts"],"names":[],"mappings":"AAEA,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAGxF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
/** Verifies that SHA-256(codeVerifier) == codeChallenge (base64url-encoded). */
|
|
3
|
+
export function verifyCodeChallenge(codeVerifier, codeChallenge) {
|
|
4
|
+
const computed = createHash("sha256").update(codeVerifier).digest("base64url");
|
|
5
|
+
return computed === codeChallenge;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=pkce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../../src/auth/oauth/pkce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,gFAAgF;AAChF,MAAM,UAAU,mBAAmB,CAAC,YAAoB,EAAE,aAAqB;IAC7E,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAC/E,OAAO,QAAQ,KAAK,aAAa,CAAC;AACpC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FastifyRequest, FastifyReply } from "fastify";
|
|
2
|
+
/**
|
|
3
|
+
* Dynamic Client Registration endpoint (RFC 7591).
|
|
4
|
+
* Accepts any well-formed registration (public clients only, no secret).
|
|
5
|
+
* Used by MCP Inspector and other clients that don't support CIMD.
|
|
6
|
+
*/
|
|
7
|
+
export declare function handleClientRegistration(request: FastifyRequest, reply: FastifyReply): Promise<void>;
|
|
8
|
+
//# sourceMappingURL=register.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/register.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAa5D;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,IAAI,CAAC,CAyBf"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { registerClient } from "../storage/oauth-client.js";
|
|
3
|
+
import { log } from "../../utils.js";
|
|
4
|
+
const registrationSchema = z.object({
|
|
5
|
+
redirect_uris: z.array(z.string().min(1)).min(1),
|
|
6
|
+
client_name: z.string().optional(),
|
|
7
|
+
token_endpoint_auth_method: z.string().optional(),
|
|
8
|
+
grant_types: z.array(z.string()).optional(),
|
|
9
|
+
response_types: z.array(z.string()).optional(),
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* Dynamic Client Registration endpoint (RFC 7591).
|
|
13
|
+
* Accepts any well-formed registration (public clients only, no secret).
|
|
14
|
+
* Used by MCP Inspector and other clients that don't support CIMD.
|
|
15
|
+
*/
|
|
16
|
+
export async function handleClientRegistration(request, reply) {
|
|
17
|
+
const parsed = registrationSchema.safeParse(request.body);
|
|
18
|
+
if (!parsed.success) {
|
|
19
|
+
return reply.status(400).send({
|
|
20
|
+
error: "invalid_client_metadata",
|
|
21
|
+
error_description: parsed.error.issues.map((i) => i.message).join("; "),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
const { redirect_uris, client_name } = parsed.data;
|
|
25
|
+
const client = registerClient({ redirectUris: redirect_uris, clientName: client_name });
|
|
26
|
+
log(`DCR: registered client=${client.clientId} name=${client_name ?? "(none)"} uris=${redirect_uris.join(", ")}`);
|
|
27
|
+
return reply.status(201).send({
|
|
28
|
+
client_id: client.clientId,
|
|
29
|
+
client_id_issued_at: Math.floor(client.registeredAt / 1000),
|
|
30
|
+
redirect_uris,
|
|
31
|
+
client_name: client_name ?? undefined,
|
|
32
|
+
token_endpoint_auth_method: "none",
|
|
33
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
34
|
+
response_types: ["code"],
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=register.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.js","sourceRoot":"","sources":["../../../src/auth/oauth/register.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAErC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,aAAa,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAChD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,0BAA0B,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjD,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IAC3C,cAAc,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;CAC/C,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,OAAuB,EACvB,KAAmB;IAEnB,MAAM,MAAM,GAAG,kBAAkB,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YAC5B,KAAK,EAAE,yBAAyB;YAChC,iBAAiB,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;SACxE,CAAC,CAAC;IACL,CAAC;IAED,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC;IAEnD,MAAM,MAAM,GAAG,cAAc,CAAC,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC,CAAC;IAExF,GAAG,CAAC,0BAA0B,MAAM,CAAC,QAAQ,SAAS,WAAW,IAAI,QAAQ,SAAS,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAElH,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;QAC5B,SAAS,EAAE,MAAM,CAAC,QAAQ;QAC1B,mBAAmB,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY,GAAG,IAAI,CAAC;QAC3D,aAAa;QACb,WAAW,EAAE,WAAW,IAAI,SAAS;QACrC,0BAA0B,EAAE,MAAM;QAClC,WAAW,EAAE,CAAC,oBAAoB,EAAE,eAAe,CAAC;QACpD,cAAc,EAAE,CAAC,MAAM,CAAC;KACzB,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/token.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AA0B5D,wBAAsB,WAAW,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAc7F"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { consumeAuthorizationCode } from "../storage/authorization-code.js";
|
|
3
|
+
import { createRefreshToken, rotateRefreshToken } from "../storage/refresh-token.js";
|
|
4
|
+
import { findConnectedAccountByUserId } from "../storage/connected-account.js";
|
|
5
|
+
import { signMcpAccessToken } from "../tokens/jwt.js";
|
|
6
|
+
import { verifyCodeChallenge } from "./pkce.js";
|
|
7
|
+
import { isKnownClient } from "./cimd.js";
|
|
8
|
+
import { findClient } from "../storage/oauth-client.js";
|
|
9
|
+
import { CLAUDE_CLIENT_ID, ACCESS_TOKEN_TTL_SECONDS } from "../../config/oauth.js";
|
|
10
|
+
import { log } from "../../utils.js";
|
|
11
|
+
const authCodeSchema = z.object({
|
|
12
|
+
grant_type: z.literal("authorization_code"),
|
|
13
|
+
code: z.string().min(1),
|
|
14
|
+
redirect_uri: z.string().url(),
|
|
15
|
+
client_id: z.string(),
|
|
16
|
+
code_verifier: z.string().min(43).max(128),
|
|
17
|
+
});
|
|
18
|
+
const refreshSchema = z.object({
|
|
19
|
+
grant_type: z.literal("refresh_token"),
|
|
20
|
+
refresh_token: z.string().min(1),
|
|
21
|
+
client_id: z.string(),
|
|
22
|
+
});
|
|
23
|
+
export async function handleToken(request, reply) {
|
|
24
|
+
const body = request.body;
|
|
25
|
+
if (body.grant_type === "authorization_code") {
|
|
26
|
+
return exchangeAuthCode(body, reply);
|
|
27
|
+
}
|
|
28
|
+
if (body.grant_type === "refresh_token") {
|
|
29
|
+
return exchangeRefreshToken(body, reply);
|
|
30
|
+
}
|
|
31
|
+
return reply.status(400).send({
|
|
32
|
+
error: "unsupported_grant_type",
|
|
33
|
+
error_description: "Supported grant types: authorization_code, refresh_token",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
async function exchangeAuthCode(body, reply) {
|
|
37
|
+
const parsed = authCodeSchema.safeParse(body);
|
|
38
|
+
if (!parsed.success) {
|
|
39
|
+
return reply.status(400).send({ error: "invalid_request", error_description: parsed.error.message });
|
|
40
|
+
}
|
|
41
|
+
const { code, redirect_uri, client_id, code_verifier } = parsed.data;
|
|
42
|
+
if (!isKnownClient(client_id, [CLAUDE_CLIENT_ID], (id) => !!findClient(id))) {
|
|
43
|
+
return reply.status(401).send({ error: "invalid_client" });
|
|
44
|
+
}
|
|
45
|
+
const authCode = consumeAuthorizationCode(code);
|
|
46
|
+
if (!authCode) {
|
|
47
|
+
return reply
|
|
48
|
+
.status(400)
|
|
49
|
+
.send({ error: "invalid_grant", error_description: "Code expired, consumed, or not found" });
|
|
50
|
+
}
|
|
51
|
+
if (authCode.redirectUri !== redirect_uri) {
|
|
52
|
+
return reply.status(400).send({ error: "invalid_grant", error_description: "redirect_uri mismatch" });
|
|
53
|
+
}
|
|
54
|
+
if (authCode.clientId !== client_id) {
|
|
55
|
+
return reply.status(400).send({ error: "invalid_grant", error_description: "client_id mismatch" });
|
|
56
|
+
}
|
|
57
|
+
if (!verifyCodeChallenge(code_verifier, authCode.codeChallenge)) {
|
|
58
|
+
return reply
|
|
59
|
+
.status(400)
|
|
60
|
+
.send({ error: "invalid_grant", error_description: "PKCE verification failed" });
|
|
61
|
+
}
|
|
62
|
+
const account = findConnectedAccountByUserId(authCode.userId);
|
|
63
|
+
if (!account) {
|
|
64
|
+
return reply.status(400).send({ error: "invalid_grant", error_description: "User account not found" });
|
|
65
|
+
}
|
|
66
|
+
const accessToken = await signMcpAccessToken({
|
|
67
|
+
sub: account.userId,
|
|
68
|
+
email: account.email,
|
|
69
|
+
name: account.name,
|
|
70
|
+
});
|
|
71
|
+
const refreshToken = createRefreshToken({ userId: account.userId, clientId: client_id });
|
|
72
|
+
log(`MCP token issued for ${account.email}`);
|
|
73
|
+
return reply.send({
|
|
74
|
+
access_token: accessToken,
|
|
75
|
+
refresh_token: refreshToken.token,
|
|
76
|
+
token_type: "Bearer",
|
|
77
|
+
expires_in: ACCESS_TOKEN_TTL_SECONDS,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async function exchangeRefreshToken(body, reply) {
|
|
81
|
+
const parsed = refreshSchema.safeParse(body);
|
|
82
|
+
if (!parsed.success) {
|
|
83
|
+
return reply.status(400).send({ error: "invalid_request", error_description: parsed.error.message });
|
|
84
|
+
}
|
|
85
|
+
const { refresh_token, client_id } = parsed.data;
|
|
86
|
+
if (!isKnownClient(client_id, [CLAUDE_CLIENT_ID], (id) => !!findClient(id))) {
|
|
87
|
+
return reply.status(401).send({ error: "invalid_client" });
|
|
88
|
+
}
|
|
89
|
+
const newRefreshToken = rotateRefreshToken(refresh_token);
|
|
90
|
+
if (!newRefreshToken) {
|
|
91
|
+
return reply
|
|
92
|
+
.status(400)
|
|
93
|
+
.send({ error: "invalid_grant", error_description: "Refresh token expired or revoked" });
|
|
94
|
+
}
|
|
95
|
+
const account = findConnectedAccountByUserId(newRefreshToken.userId);
|
|
96
|
+
if (!account) {
|
|
97
|
+
return reply.status(400).send({ error: "invalid_grant", error_description: "User account not found" });
|
|
98
|
+
}
|
|
99
|
+
const accessToken = await signMcpAccessToken({
|
|
100
|
+
sub: account.userId,
|
|
101
|
+
email: account.email,
|
|
102
|
+
name: account.name,
|
|
103
|
+
});
|
|
104
|
+
return reply.send({
|
|
105
|
+
access_token: accessToken,
|
|
106
|
+
refresh_token: newRefreshToken.token,
|
|
107
|
+
token_type: "Bearer",
|
|
108
|
+
expires_in: ACCESS_TOKEN_TTL_SECONDS,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../../src/auth/oauth/token.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface PendingAuthSession {
|
|
2
|
+
clientId: string;
|
|
3
|
+
redirectUri: string;
|
|
4
|
+
state: string;
|
|
5
|
+
codeChallenge: string;
|
|
6
|
+
codeChallengeMethod: "S256";
|
|
7
|
+
expiresAt: number;
|
|
8
|
+
/** The MCP server base URL as seen by the incoming request — used for the Google redirect_uri. */
|
|
9
|
+
serverBaseUrl: string;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,kGAAkG;IAClG,aAAa,EAAE,MAAM,CAAC;CACvB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/auth/oauth/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"userinfo.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/userinfo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAI5D,wBAAsB,cAAc,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BhG"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { verifyMcpAccessToken } from "../tokens/jwt.js";
|
|
2
|
+
import { findConnectedAccountByUserId } from "../storage/connected-account.js";
|
|
3
|
+
export async function handleUserInfo(request, reply) {
|
|
4
|
+
const header = request.headers.authorization;
|
|
5
|
+
if (!header?.startsWith("Bearer ")) {
|
|
6
|
+
return reply
|
|
7
|
+
.status(401)
|
|
8
|
+
.header("WWW-Authenticate", "Bearer")
|
|
9
|
+
.send({ error: "unauthorized" });
|
|
10
|
+
}
|
|
11
|
+
const token = header.slice(7).trim();
|
|
12
|
+
const payload = await verifyMcpAccessToken(token);
|
|
13
|
+
if (!payload) {
|
|
14
|
+
return reply
|
|
15
|
+
.status(401)
|
|
16
|
+
.header("WWW-Authenticate", 'Bearer error="invalid_token"')
|
|
17
|
+
.send({ error: "invalid_token" });
|
|
18
|
+
}
|
|
19
|
+
const account = findConnectedAccountByUserId(payload.sub);
|
|
20
|
+
if (!account) {
|
|
21
|
+
return reply.status(404).send({ error: "user_not_found" });
|
|
22
|
+
}
|
|
23
|
+
return reply.send({
|
|
24
|
+
sub: account.userId,
|
|
25
|
+
email: account.email,
|
|
26
|
+
name: account.name,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=userinfo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"userinfo.js","sourceRoot":"","sources":["../../../src/auth/oauth/userinfo.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,4BAA4B,EAAE,MAAM,iCAAiC,CAAC;AAE/E,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAAuB,EAAE,KAAmB;IAC/E,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;IAE7C,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACnC,OAAO,KAAK;aACT,MAAM,CAAC,GAAG,CAAC;aACX,MAAM,CAAC,kBAAkB,EAAE,QAAQ,CAAC;aACpC,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;IACrC,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACrC,MAAM,OAAO,GAAG,MAAM,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAElD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,KAAK;aACT,MAAM,CAAC,GAAG,CAAC;aACX,MAAM,CAAC,kBAAkB,EAAE,8BAA8B,CAAC;aAC1D,IAAI,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,OAAO,GAAG,4BAA4B,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC1D,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC;QAChB,GAAG,EAAE,OAAO,CAAC,MAAM;QACnB,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,IAAI,EAAE,OAAO,CAAC,IAAI;KACnB,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface AuthorizationCode {
|
|
2
|
+
id: string;
|
|
3
|
+
code: string;
|
|
4
|
+
userId: string;
|
|
5
|
+
clientId: string;
|
|
6
|
+
redirectUri: string;
|
|
7
|
+
codeChallenge: string;
|
|
8
|
+
expiresAt: number;
|
|
9
|
+
consumedAt: number | null;
|
|
10
|
+
}
|
|
11
|
+
export declare function createAuthorizationCode(params: {
|
|
12
|
+
userId: string;
|
|
13
|
+
clientId: string;
|
|
14
|
+
redirectUri: string;
|
|
15
|
+
codeChallenge: string;
|
|
16
|
+
}): AuthorizationCode;
|
|
17
|
+
/** Finds the code, marks it consumed (one-time), and returns it. Returns null if invalid. */
|
|
18
|
+
export declare function consumeAuthorizationCode(code: string): AuthorizationCode | null;
|
|
19
|
+
//# sourceMappingURL=authorization-code.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorization-code.d.ts","sourceRoot":"","sources":["../../../src/auth/storage/authorization-code.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AA0BD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB,GAAG,iBAAiB,CAapB;AAED,6FAA6F;AAC7F,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAmB/E"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
2
|
+
import { getDb } from "./db.js";
|
|
3
|
+
import { AUTH_CODE_TTL_SECONDS } from "../../config/oauth.js";
|
|
4
|
+
function rowToCode(row) {
|
|
5
|
+
return {
|
|
6
|
+
id: row.id,
|
|
7
|
+
code: row.code,
|
|
8
|
+
userId: row.user_id,
|
|
9
|
+
clientId: row.client_id,
|
|
10
|
+
redirectUri: row.redirect_uri,
|
|
11
|
+
codeChallenge: row.code_challenge,
|
|
12
|
+
expiresAt: row.expires_at,
|
|
13
|
+
consumedAt: row.consumed_at,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function createAuthorizationCode(params) {
|
|
17
|
+
const db = getDb();
|
|
18
|
+
const id = randomUUID();
|
|
19
|
+
const code = randomBytes(32).toString("base64url");
|
|
20
|
+
const expiresAt = Date.now() + AUTH_CODE_TTL_SECONDS * 1000;
|
|
21
|
+
db.prepare(`
|
|
22
|
+
INSERT INTO authorization_codes
|
|
23
|
+
(id, code, user_id, client_id, redirect_uri, code_challenge, expires_at, consumed_at)
|
|
24
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, NULL)
|
|
25
|
+
`).run(id, code, params.userId, params.clientId, params.redirectUri, params.codeChallenge, expiresAt);
|
|
26
|
+
return { id, code, ...params, expiresAt, consumedAt: null };
|
|
27
|
+
}
|
|
28
|
+
/** Finds the code, marks it consumed (one-time), and returns it. Returns null if invalid. */
|
|
29
|
+
export function consumeAuthorizationCode(code) {
|
|
30
|
+
const db = getDb();
|
|
31
|
+
const row = db
|
|
32
|
+
.prepare("SELECT * FROM authorization_codes WHERE code = ?")
|
|
33
|
+
.get(code);
|
|
34
|
+
if (!row)
|
|
35
|
+
return null;
|
|
36
|
+
const authCode = rowToCode(row);
|
|
37
|
+
if (authCode.consumedAt !== null)
|
|
38
|
+
return null;
|
|
39
|
+
if (authCode.expiresAt < Date.now())
|
|
40
|
+
return null;
|
|
41
|
+
db.prepare("UPDATE authorization_codes SET consumed_at = ? WHERE id = ?").run(Date.now(), authCode.id);
|
|
42
|
+
return authCode;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=authorization-code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorization-code.js","sourceRoot":"","sources":["../../../src/auth/storage/authorization-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAwB9D,SAAS,SAAS,CAAC,GAAU;IAC3B,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,MAAM,EAAE,GAAG,CAAC,OAAO;QACnB,QAAQ,EAAE,GAAG,CAAC,SAAS;QACvB,WAAW,EAAE,GAAG,CAAC,YAAY;QAC7B,aAAa,EAAE,GAAG,CAAC,cAAc;QACjC,SAAS,EAAE,GAAG,CAAC,UAAU;QACzB,UAAU,EAAE,GAAG,CAAC,WAAW;KAC5B,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,MAKvC;IACC,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC;IACxB,MAAM,IAAI,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACnD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,qBAAqB,GAAG,IAAI,CAAC;IAE5D,EAAE,CAAC,OAAO,CAAC;;;;GAIV,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;IAEtG,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;AAC9D,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,wBAAwB,CAAC,IAAY;IACnD,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,MAAM,GAAG,GAAG,EAAE;SACX,OAAO,CAAkB,kDAAkD,CAAC;SAC5E,GAAG,CAAC,IAAI,CAAC,CAAC;IAEb,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAEtB,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAEhC,IAAI,QAAQ,CAAC,UAAU,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC9C,IAAI,QAAQ,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,IAAI,CAAC;IAEjD,EAAE,CAAC,OAAO,CAAC,6DAA6D,CAAC,CAAC,GAAG,CAC3E,IAAI,CAAC,GAAG,EAAE,EACV,QAAQ,CAAC,EAAE,CACZ,CAAC;IAEF,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface ConnectedAccount {
|
|
2
|
+
id: string;
|
|
3
|
+
userId: string;
|
|
4
|
+
fleetxAccessToken: string;
|
|
5
|
+
fleetxTokenExpiry: number | null;
|
|
6
|
+
email: string;
|
|
7
|
+
name: string;
|
|
8
|
+
createdAt: number;
|
|
9
|
+
updatedAt: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function upsertConnectedAccount(params: {
|
|
12
|
+
userId: string;
|
|
13
|
+
fleetxAccessToken: string;
|
|
14
|
+
fleetxTokenExpiry?: number;
|
|
15
|
+
email: string;
|
|
16
|
+
name: string;
|
|
17
|
+
}): ConnectedAccount;
|
|
18
|
+
export declare function findConnectedAccountByUserId(userId: string): ConnectedAccount | null;
|
|
19
|
+
/**
|
|
20
|
+
* Clears the stored FleetX access token for a user.
|
|
21
|
+
* Called when FleetX returns 401 — forces re-authentication on next MCP connect.
|
|
22
|
+
*/
|
|
23
|
+
export declare function expireConnectedAccountToken(userId: string): void;
|
|
24
|
+
//# sourceMappingURL=connected-account.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connected-account.d.ts","sourceRoot":"","sources":["../../../src/auth/storage/connected-account.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AA2BD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,gBAAgB,CA4CnB;AAED,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAMpF;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAKhE"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { getDb } from "./db.js";
|
|
3
|
+
import { encryptToken, decryptToken } from "../tokens/encryption.js";
|
|
4
|
+
import { getOAuthConfig } from "../../config/oauth.js";
|
|
5
|
+
function decrypt(row) {
|
|
6
|
+
const { TOKEN_ENCRYPTION_KEY: key } = getOAuthConfig();
|
|
7
|
+
return {
|
|
8
|
+
id: row.id,
|
|
9
|
+
userId: row.user_id,
|
|
10
|
+
fleetxAccessToken: decryptToken(row.fleetx_access_token, key),
|
|
11
|
+
fleetxTokenExpiry: row.fleetx_token_expiry,
|
|
12
|
+
email: row.email,
|
|
13
|
+
name: row.name,
|
|
14
|
+
createdAt: row.created_at,
|
|
15
|
+
updatedAt: row.updated_at,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function upsertConnectedAccount(params) {
|
|
19
|
+
const db = getDb();
|
|
20
|
+
const { TOKEN_ENCRYPTION_KEY: key } = getOAuthConfig();
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
const existing = db
|
|
23
|
+
.prepare("SELECT id FROM connected_accounts WHERE user_id = ?")
|
|
24
|
+
.get(params.userId);
|
|
25
|
+
if (existing) {
|
|
26
|
+
db.prepare(`
|
|
27
|
+
UPDATE connected_accounts
|
|
28
|
+
SET fleetx_access_token = ?,
|
|
29
|
+
fleetx_token_expiry = ?,
|
|
30
|
+
email = ?,
|
|
31
|
+
name = ?,
|
|
32
|
+
updated_at = ?
|
|
33
|
+
WHERE user_id = ?
|
|
34
|
+
`).run(encryptToken(params.fleetxAccessToken, key), params.fleetxTokenExpiry ?? null, params.email, params.name, now, params.userId);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
db.prepare(`
|
|
38
|
+
INSERT INTO connected_accounts
|
|
39
|
+
(id, user_id, fleetx_access_token, fleetx_token_expiry, email, name, created_at, updated_at)
|
|
40
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
41
|
+
`).run(randomUUID(), params.userId, encryptToken(params.fleetxAccessToken, key), params.fleetxTokenExpiry ?? null, params.email, params.name, now, now);
|
|
42
|
+
}
|
|
43
|
+
return findConnectedAccountByUserId(params.userId);
|
|
44
|
+
}
|
|
45
|
+
export function findConnectedAccountByUserId(userId) {
|
|
46
|
+
const db = getDb();
|
|
47
|
+
const row = db
|
|
48
|
+
.prepare("SELECT * FROM connected_accounts WHERE user_id = ?")
|
|
49
|
+
.get(userId);
|
|
50
|
+
return row ? decrypt(row) : null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Clears the stored FleetX access token for a user.
|
|
54
|
+
* Called when FleetX returns 401 — forces re-authentication on next MCP connect.
|
|
55
|
+
*/
|
|
56
|
+
export function expireConnectedAccountToken(userId) {
|
|
57
|
+
const db = getDb();
|
|
58
|
+
db.prepare("UPDATE connected_accounts SET fleetx_access_token = '', updated_at = ? WHERE user_id = ?").run(Date.now(), userId);
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=connected-account.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connected-account.js","sourceRoot":"","sources":["../../../src/auth/storage/connected-account.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAwBvD,SAAS,OAAO,CAAC,GAAU;IACzB,MAAM,EAAE,oBAAoB,EAAE,GAAG,EAAE,GAAG,cAAc,EAAE,CAAC;IACvD,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,MAAM,EAAE,GAAG,CAAC,OAAO;QACnB,iBAAiB,EAAE,YAAY,CAAC,GAAG,CAAC,mBAAmB,EAAE,GAAG,CAAC;QAC7D,iBAAiB,EAAE,GAAG,CAAC,mBAAmB;QAC1C,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,SAAS,EAAE,GAAG,CAAC,UAAU;QACzB,SAAS,EAAE,GAAG,CAAC,UAAU;KAC1B,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,MAMtC;IACC,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,MAAM,EAAE,oBAAoB,EAAE,GAAG,EAAE,GAAG,cAAc,EAAE,CAAC;IACvD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEvB,MAAM,QAAQ,GAAG,EAAE;SAChB,OAAO,CAA2B,qDAAqD,CAAC;SACxF,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAEtB,IAAI,QAAQ,EAAE,CAAC;QACb,EAAE,CAAC,OAAO,CAAC;;;;;;;;KAQV,CAAC,CAAC,GAAG,CACJ,YAAY,CAAC,MAAM,CAAC,iBAAiB,EAAE,GAAG,CAAC,EAC3C,MAAM,CAAC,iBAAiB,IAAI,IAAI,EAChC,MAAM,CAAC,KAAK,EACZ,MAAM,CAAC,IAAI,EACX,GAAG,EACH,MAAM,CAAC,MAAM,CACd,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,EAAE,CAAC,OAAO,CAAC;;;;KAIV,CAAC,CAAC,GAAG,CACJ,UAAU,EAAE,EACZ,MAAM,CAAC,MAAM,EACb,YAAY,CAAC,MAAM,CAAC,iBAAiB,EAAE,GAAG,CAAC,EAC3C,MAAM,CAAC,iBAAiB,IAAI,IAAI,EAChC,MAAM,CAAC,KAAK,EACZ,MAAM,CAAC,IAAI,EACX,GAAG,EACH,GAAG,CACJ,CAAC;IACJ,CAAC;IAED,OAAO,4BAA4B,CAAC,MAAM,CAAC,MAAM,CAAE,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,4BAA4B,CAAC,MAAc;IACzD,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,MAAM,GAAG,GAAG,EAAE;SACX,OAAO,CAAkB,oDAAoD,CAAC;SAC9E,GAAG,CAAC,MAAM,CAAC,CAAC;IACf,OAAO,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,2BAA2B,CAAC,MAAc;IACxD,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,EAAE,CAAC,OAAO,CACR,0FAA0F,CAC3F,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC;AAC5B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../../src/auth/storage/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAQtC,wBAAgB,KAAK,IAAI,QAAQ,CAAC,QAAQ,CAiBzC"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import { getOAuthConfig } from "../../config/oauth.js";
|
|
5
|
+
import { log } from "../../utils.js";
|
|
6
|
+
let _db = null;
|
|
7
|
+
export function getDb() {
|
|
8
|
+
if (_db)
|
|
9
|
+
return _db;
|
|
10
|
+
const { DATABASE_PATH } = getOAuthConfig();
|
|
11
|
+
const dir = path.dirname(DATABASE_PATH);
|
|
12
|
+
if (!fs.existsSync(dir)) {
|
|
13
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
_db = new Database(DATABASE_PATH);
|
|
16
|
+
_db.pragma("journal_mode = WAL");
|
|
17
|
+
_db.pragma("foreign_keys = ON");
|
|
18
|
+
runMigrations(_db);
|
|
19
|
+
log("SQLite database ready:", DATABASE_PATH);
|
|
20
|
+
return _db;
|
|
21
|
+
}
|
|
22
|
+
function runMigrations(db) {
|
|
23
|
+
// M001: drop NOT NULL on fleetx_refresh_token (FleetX API no longer returns one)
|
|
24
|
+
migrateConnectedAccountsSchema(db);
|
|
25
|
+
db.exec(`
|
|
26
|
+
CREATE TABLE IF NOT EXISTS connected_accounts (
|
|
27
|
+
id TEXT PRIMARY KEY,
|
|
28
|
+
user_id TEXT NOT NULL UNIQUE,
|
|
29
|
+
fleetx_access_token TEXT NOT NULL,
|
|
30
|
+
fleetx_refresh_token TEXT,
|
|
31
|
+
fleetx_token_expiry INTEGER,
|
|
32
|
+
email TEXT NOT NULL,
|
|
33
|
+
name TEXT NOT NULL,
|
|
34
|
+
created_at INTEGER NOT NULL,
|
|
35
|
+
updated_at INTEGER NOT NULL
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
CREATE TABLE IF NOT EXISTS authorization_codes (
|
|
39
|
+
id TEXT PRIMARY KEY,
|
|
40
|
+
code TEXT NOT NULL UNIQUE,
|
|
41
|
+
user_id TEXT NOT NULL,
|
|
42
|
+
client_id TEXT NOT NULL,
|
|
43
|
+
redirect_uri TEXT NOT NULL,
|
|
44
|
+
code_challenge TEXT NOT NULL,
|
|
45
|
+
expires_at INTEGER NOT NULL,
|
|
46
|
+
consumed_at INTEGER
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
CREATE INDEX IF NOT EXISTS idx_auth_codes_code
|
|
50
|
+
ON authorization_codes(code);
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_auth_codes_expires
|
|
52
|
+
ON authorization_codes(expires_at);
|
|
53
|
+
|
|
54
|
+
CREATE TABLE IF NOT EXISTS refresh_tokens (
|
|
55
|
+
id TEXT PRIMARY KEY,
|
|
56
|
+
token TEXT NOT NULL UNIQUE,
|
|
57
|
+
user_id TEXT NOT NULL,
|
|
58
|
+
client_id TEXT NOT NULL,
|
|
59
|
+
expires_at INTEGER NOT NULL,
|
|
60
|
+
revoked_at INTEGER
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_token
|
|
64
|
+
ON refresh_tokens(token);
|
|
65
|
+
|
|
66
|
+
CREATE TABLE IF NOT EXISTS oauth_clients (
|
|
67
|
+
client_id TEXT PRIMARY KEY,
|
|
68
|
+
redirect_uris TEXT NOT NULL,
|
|
69
|
+
client_name TEXT,
|
|
70
|
+
registered_at INTEGER NOT NULL
|
|
71
|
+
);
|
|
72
|
+
`);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* SQLite cannot ALTER COLUMN to drop NOT NULL.
|
|
76
|
+
* If connected_accounts has fleetx_refresh_token as NOT NULL, recreate the table.
|
|
77
|
+
*/
|
|
78
|
+
function migrateConnectedAccountsSchema(db) {
|
|
79
|
+
const cols = db.pragma("table_info(connected_accounts)");
|
|
80
|
+
if (cols.length === 0)
|
|
81
|
+
return; // table doesn't exist yet — nothing to migrate
|
|
82
|
+
const col = cols.find((c) => c.name === "fleetx_refresh_token");
|
|
83
|
+
if (!col || col.notnull === 0)
|
|
84
|
+
return; // already nullable — no migration needed
|
|
85
|
+
log("M001: migrating connected_accounts to drop NOT NULL on fleetx_refresh_token");
|
|
86
|
+
db.exec(`
|
|
87
|
+
ALTER TABLE connected_accounts RENAME TO connected_accounts_old;
|
|
88
|
+
|
|
89
|
+
CREATE TABLE connected_accounts (
|
|
90
|
+
id TEXT PRIMARY KEY,
|
|
91
|
+
user_id TEXT NOT NULL UNIQUE,
|
|
92
|
+
fleetx_access_token TEXT NOT NULL,
|
|
93
|
+
fleetx_refresh_token TEXT,
|
|
94
|
+
fleetx_token_expiry INTEGER,
|
|
95
|
+
email TEXT NOT NULL,
|
|
96
|
+
name TEXT NOT NULL,
|
|
97
|
+
created_at INTEGER NOT NULL,
|
|
98
|
+
updated_at INTEGER NOT NULL
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
INSERT INTO connected_accounts
|
|
102
|
+
SELECT id, user_id, fleetx_access_token, fleetx_refresh_token,
|
|
103
|
+
fleetx_token_expiry, email, name, created_at, updated_at
|
|
104
|
+
FROM connected_accounts_old;
|
|
105
|
+
|
|
106
|
+
DROP TABLE connected_accounts_old;
|
|
107
|
+
`);
|
|
108
|
+
log("M001: migration complete");
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=db.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../../../src/auth/storage/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAErC,IAAI,GAAG,GAA6B,IAAI,CAAC;AAEzC,MAAM,UAAU,KAAK;IACnB,IAAI,GAAG;QAAE,OAAO,GAAG,CAAC;IAEpB,MAAM,EAAE,aAAa,EAAE,GAAG,cAAc,EAAE,CAAC;IAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAExC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,GAAG,GAAG,IAAI,QAAQ,CAAC,aAAa,CAAC,CAAC;IAClC,GAAG,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IACjC,GAAG,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAEhC,aAAa,CAAC,GAAG,CAAC,CAAC;IACnB,GAAG,CAAC,wBAAwB,EAAE,aAAa,CAAC,CAAC;IAC7C,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,aAAa,CAAC,EAAqB;IAC1C,iFAAiF;IACjF,8BAA8B,CAAC,EAAE,CAAC,CAAC;IAEnC,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CP,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,SAAS,8BAA8B,CAAC,EAAqB;IAE3D,MAAM,IAAI,GAAG,EAAE,CAAC,MAAM,CAAC,gCAAgC,CAAc,CAAC;IACtE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,+CAA+C;IAE9E,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,sBAAsB,CAAC,CAAC;IAChE,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,KAAK,CAAC;QAAE,OAAO,CAAC,yCAAyC;IAEhF,GAAG,CAAC,6EAA6E,CAAC,CAAC;IAEnF,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;GAqBP,CAAC,CAAC;IAEH,GAAG,CAAC,0BAA0B,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface OAuthClient {
|
|
2
|
+
clientId: string;
|
|
3
|
+
redirectUris: string[];
|
|
4
|
+
clientName: string | null;
|
|
5
|
+
registeredAt: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function registerClient(params: {
|
|
8
|
+
redirectUris: string[];
|
|
9
|
+
clientName?: string;
|
|
10
|
+
}): OAuthClient;
|
|
11
|
+
export declare function findClient(clientId: string): OAuthClient | null;
|
|
12
|
+
//# sourceMappingURL=oauth-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-client.d.ts","sourceRoot":"","sources":["../../../src/auth/storage/oauth-client.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB;AAkBD,wBAAgB,cAAc,CAAC,MAAM,EAAE;IACrC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,WAAW,CAWd;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAM/D"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { getDb } from "./db.js";
|
|
3
|
+
function rowToClient(row) {
|
|
4
|
+
return {
|
|
5
|
+
clientId: row.client_id,
|
|
6
|
+
redirectUris: JSON.parse(row.redirect_uris),
|
|
7
|
+
clientName: row.client_name,
|
|
8
|
+
registeredAt: row.registered_at,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function registerClient(params) {
|
|
12
|
+
const db = getDb();
|
|
13
|
+
const clientId = randomUUID();
|
|
14
|
+
const now = Date.now();
|
|
15
|
+
db.prepare(`
|
|
16
|
+
INSERT INTO oauth_clients (client_id, redirect_uris, client_name, registered_at)
|
|
17
|
+
VALUES (?, ?, ?, ?)
|
|
18
|
+
`).run(clientId, JSON.stringify(params.redirectUris), params.clientName ?? null, now);
|
|
19
|
+
return { clientId, redirectUris: params.redirectUris, clientName: params.clientName ?? null, registeredAt: now };
|
|
20
|
+
}
|
|
21
|
+
export function findClient(clientId) {
|
|
22
|
+
const db = getDb();
|
|
23
|
+
const row = db
|
|
24
|
+
.prepare("SELECT * FROM oauth_clients WHERE client_id = ?")
|
|
25
|
+
.get(clientId);
|
|
26
|
+
return row ? rowToClient(row) : null;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=oauth-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-client.js","sourceRoot":"","sources":["../../../src/auth/storage/oauth-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAgBhC,SAAS,WAAW,CAAC,GAAU;IAC7B,OAAO;QACL,QAAQ,EAAE,GAAG,CAAC,SAAS;QACvB,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,CAAa;QACvD,UAAU,EAAE,GAAG,CAAC,WAAW;QAC3B,YAAY,EAAE,GAAG,CAAC,aAAa;KAChC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAG9B;IACC,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,MAAM,QAAQ,GAAG,UAAU,EAAE,CAAC;IAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEvB,EAAE,CAAC,OAAO,CAAC;;;GAGV,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,UAAU,IAAI,IAAI,EAAE,GAAG,CAAC,CAAC;IAEtF,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,CAAC,YAAY,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC;AACnH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,QAAgB;IACzC,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,MAAM,GAAG,GAAG,EAAE;SACX,OAAO,CAAkB,iDAAiD,CAAC;SAC3E,GAAG,CAAC,QAAQ,CAAC,CAAC;IACjB,OAAO,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACvC,CAAC"}
|