@hammadj/better-auth-oauth-provider 1.5.0-beta.9

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.
@@ -0,0 +1,298 @@
1
+ import { isAPIError } from "better-auth/api";
2
+ import { verifyAccessToken } from "better-auth/oauth2";
3
+ import { APIError as APIError$1 } from "better-call";
4
+ import { BetterAuthError } from "@better-auth/core/error";
5
+ import { base64, base64Url } from "@better-auth/utils/base64";
6
+ import { createHash } from "@better-auth/utils/hash";
7
+ import { constantTimeEqual, symmetricDecrypt, symmetricEncrypt } from "better-auth/crypto";
8
+
9
+ //#region src/mcp.ts
10
+ /**
11
+ * A request middleware handler that checks and responds with
12
+ * a WWW-Authenticate header for unauthenticated responses.
13
+ *
14
+ * @external
15
+ */
16
+ const mcpHandler = (verifyOptions, handler, opts) => {
17
+ return async (req) => {
18
+ const authorization = req.headers?.get("authorization") ?? void 0;
19
+ const accessToken = authorization?.startsWith("Bearer ") ? authorization.replace("Bearer ", "") : authorization;
20
+ try {
21
+ if (!accessToken?.length) throw new APIError$1("UNAUTHORIZED", { message: "missing authorization header" });
22
+ return handler(req, await verifyAccessToken(accessToken, verifyOptions));
23
+ } catch (error) {
24
+ try {
25
+ handleMcpErrors(error, verifyOptions.verifyOptions.audience, opts);
26
+ } catch (err) {
27
+ if (err instanceof APIError$1) return new Response(err.message, {
28
+ ...err,
29
+ status: err.statusCode
30
+ });
31
+ throw new Error(String(err));
32
+ }
33
+ throw new Error(String(error));
34
+ }
35
+ };
36
+ };
37
+ /**
38
+ * The following handles all MCP errors and API errors
39
+ *
40
+ * @internal
41
+ */
42
+ function handleMcpErrors(error, resource, opts) {
43
+ if (isAPIError(error) && error.status === "UNAUTHORIZED") {
44
+ const wwwAuthenticateValue = (Array.isArray(resource) ? resource : [resource]).map((v) => {
45
+ let audiencePath;
46
+ if (URL.canParse?.(v)) {
47
+ const url = new URL(v);
48
+ audiencePath = url.pathname.endsWith("/") ? url.pathname.slice(0, -1) : url.pathname;
49
+ return `Bearer resource_metadata="${url.origin}/.well-known/oauth-protected-resource${audiencePath}"`;
50
+ } else {
51
+ const resourceMetadata = opts?.resourceMetadataMappings?.[v];
52
+ if (!resourceMetadata) throw new APIError$1("INTERNAL_SERVER_ERROR", { message: `missing resource_metadata mapping for ${v}` });
53
+ return `Bearer resource_metadata=${resourceMetadata}`;
54
+ }
55
+ }).join(", ");
56
+ throw new APIError$1("UNAUTHORIZED", { message: error.message }, { "WWW-Authenticate": wwwAuthenticateValue });
57
+ } else if (error instanceof Error) throw error;
58
+ else throw new Error(error);
59
+ }
60
+
61
+ //#endregion
62
+ //#region src/utils/index.ts
63
+ var TTLCache = class {
64
+ cache = /* @__PURE__ */ new Map();
65
+ constructor() {}
66
+ set(key, value) {
67
+ this.cache.set(key, value);
68
+ }
69
+ get(key) {
70
+ const entry = this.cache.get(key);
71
+ if (!entry) return void 0;
72
+ if (entry.expiresAt && entry.expiresAt < /* @__PURE__ */ new Date()) {
73
+ this.cache.delete(key);
74
+ return;
75
+ }
76
+ return entry;
77
+ }
78
+ };
79
+ /**
80
+ * Gets the oAuth Provider Plugin
81
+ * @internal
82
+ */
83
+ const getOAuthProviderPlugin = (ctx) => {
84
+ return ctx.getPlugin("oauth-provider");
85
+ };
86
+ /**
87
+ * Gets the JWT Plugin
88
+ * @internal
89
+ */
90
+ const getJwtPlugin = (ctx) => {
91
+ const plugin = ctx.getPlugin("jwt");
92
+ if (!plugin) throw new BetterAuthError("jwt_config", "jwt plugin not found");
93
+ return plugin;
94
+ };
95
+ const cachedTrustedClients = new TTLCache();
96
+ /**
97
+ * Get a client by ID, checking trusted clients first, then database
98
+ */
99
+ async function getClient(ctx, options, clientId) {
100
+ const trustedClient = cachedTrustedClients.get(clientId);
101
+ if (trustedClient) return Object.assign({}, trustedClient);
102
+ const dbClient = await ctx.context.adapter.findOne({
103
+ model: options.schema?.oauthClient?.modelName ?? "oauthClient",
104
+ where: [{
105
+ field: "clientId",
106
+ value: clientId
107
+ }]
108
+ });
109
+ if (dbClient && options.cachedTrustedClients?.has(clientId)) cachedTrustedClients.set(clientId, Object.assign({}, dbClient));
110
+ return dbClient;
111
+ }
112
+ /**
113
+ * Default client secret hasher using SHA-256
114
+ *
115
+ * @internal
116
+ */
117
+ const defaultHasher = async (value) => {
118
+ const hash = await createHash("SHA-256").digest(new TextEncoder().encode(value));
119
+ return base64Url.encode(new Uint8Array(hash), { padding: false });
120
+ };
121
+ /**
122
+ * Decrypts a storedClientSecret for signing
123
+ *
124
+ * @internal
125
+ */
126
+ async function decryptStoredClientSecret(ctx, storageMethod, storedClientSecret) {
127
+ if (storageMethod === "encrypted") return await symmetricDecrypt({
128
+ key: ctx.context.secret,
129
+ data: storedClientSecret
130
+ });
131
+ else if (typeof storageMethod === "object" && "decrypt" in storageMethod) return await storageMethod.decrypt(storedClientSecret);
132
+ throw new BetterAuthError(`Unsupported decryption storageMethod type '${storageMethod}'`);
133
+ }
134
+ /**
135
+ * Verify stored client secret against provided client secret
136
+ *
137
+ * @internal
138
+ */
139
+ async function verifyStoredClientSecret(ctx, opts, storedClientSecret, clientSecret) {
140
+ const storageMethod = opts.storeClientSecret ?? (opts.disableJwtPlugin ? "encrypted" : "hashed");
141
+ if (clientSecret && opts.prefix?.clientSecret) if (clientSecret.startsWith(opts.prefix?.clientSecret)) clientSecret = clientSecret.replace(opts.prefix.clientSecret, "");
142
+ else throw new APIError$1("UNAUTHORIZED", {
143
+ error_description: "invalid client_secret",
144
+ error: "invalid_client"
145
+ });
146
+ if (storageMethod === "hashed") {
147
+ const hashedClientSecret = clientSecret ? await defaultHasher(clientSecret) : void 0;
148
+ return !!hashedClientSecret && constantTimeEqual(hashedClientSecret, storedClientSecret);
149
+ } else if (typeof storageMethod === "object" && "hash" in storageMethod) if (storageMethod.verify) return !!clientSecret && await storageMethod.verify(clientSecret, storedClientSecret);
150
+ else {
151
+ const hashedClientSecret = clientSecret ? await storageMethod.hash(clientSecret) : void 0;
152
+ return !!hashedClientSecret && constantTimeEqual(hashedClientSecret, storedClientSecret);
153
+ }
154
+ else if (storageMethod === "encrypted" || typeof storageMethod === "object" && "decrypt" in storageMethod) {
155
+ const decryptedClientSecret = await decryptStoredClientSecret(ctx, storageMethod, storedClientSecret);
156
+ return !!clientSecret && constantTimeEqual(decryptedClientSecret, clientSecret);
157
+ }
158
+ throw new BetterAuthError(`Unsupported verify storageMethod type '${storageMethod}'`);
159
+ }
160
+ /**
161
+ * Store client secret according to the configured storage method
162
+ *
163
+ * @internal
164
+ */
165
+ async function storeClientSecret(ctx, opts, clientSecret) {
166
+ const storageMethod = opts.storeClientSecret ?? (opts.disableJwtPlugin ? "encrypted" : "hashed");
167
+ if (storageMethod === "encrypted") return await symmetricEncrypt({
168
+ key: ctx.context.secret,
169
+ data: clientSecret
170
+ });
171
+ else if (storageMethod === "hashed") return await defaultHasher(clientSecret);
172
+ else if (typeof storageMethod === "object" && "hash" in storageMethod) return await storageMethod.hash(clientSecret);
173
+ else if (typeof storageMethod === "object" && "encrypt" in storageMethod) return await storageMethod.encrypt(clientSecret);
174
+ throw new BetterAuthError(`Unsupported storeClientSecret type '${storageMethod}'`);
175
+ }
176
+ /**
177
+ * Stores a token value (ie opaque tokens, refresh tokens, transaction tokens, verification codes)
178
+ * on the database in a secure hashed format.
179
+ *
180
+ * @internal
181
+ */
182
+ async function storeToken(storageMethod = "hashed", token, type) {
183
+ if (storageMethod === "hashed") return await defaultHasher(token);
184
+ else if (typeof storageMethod === "object" && "hash" in storageMethod) return await storageMethod.hash(token, type);
185
+ throw new BetterAuthError(`storeToken: unsupported storageMethod type '${storageMethod}'`);
186
+ }
187
+ /**
188
+ * Gets a hashed token value to find on the database.
189
+ *
190
+ * @internal
191
+ */
192
+ async function getStoredToken(storageMethod = "hashed", token, type) {
193
+ if (storageMethod === "hashed") return await defaultHasher(token);
194
+ else if (typeof storageMethod === "object" && "hash" in storageMethod) return await storageMethod.hash(token, type);
195
+ throw new BetterAuthError(`getStoredToken: unsupported storageMethod type '${storageMethod}'`);
196
+ }
197
+ /**
198
+ * Converts a BASIC authorization header
199
+ * into its client_id and client_secret representation
200
+ *
201
+ * @internal
202
+ */
203
+ function basicToClientCredentials(authorization) {
204
+ if (authorization.startsWith("Basic ")) {
205
+ const encoded = authorization.replace("Basic ", "");
206
+ const decoded = new TextDecoder().decode(base64.decode(encoded));
207
+ if (!decoded.includes(":")) throw new APIError$1("BAD_REQUEST", {
208
+ error_description: "invalid authorization header format",
209
+ error: "invalid_client"
210
+ });
211
+ const [id, secret] = decoded.split(":", 2);
212
+ if (!id || !secret) throw new APIError$1("BAD_REQUEST", {
213
+ error_description: "invalid authorization header format",
214
+ error: "invalid_client"
215
+ });
216
+ return {
217
+ client_id: id,
218
+ client_secret: secret
219
+ };
220
+ }
221
+ }
222
+ /**
223
+ * Validates client credentials failing on mismatches
224
+ * and incorrectly provided information
225
+ *
226
+ * @internal
227
+ */
228
+ async function validateClientCredentials(ctx, options, clientId, clientSecret, scopes) {
229
+ const client = await getClient(ctx, options, clientId);
230
+ if (!client) throw new APIError$1("BAD_REQUEST", {
231
+ error_description: "missing client",
232
+ error: "invalid_client"
233
+ });
234
+ if (client.disabled) throw new APIError$1("BAD_REQUEST", {
235
+ error_description: "client is disabled",
236
+ error: "invalid_client"
237
+ });
238
+ if (!client.public && !clientSecret) throw new APIError$1("BAD_REQUEST", {
239
+ error_description: "client secret must be provided",
240
+ error: "invalid_client"
241
+ });
242
+ if (clientSecret && !client.clientSecret) throw new APIError$1("BAD_REQUEST", {
243
+ error_description: "public client, client secret should not be received",
244
+ error: "invalid_client"
245
+ });
246
+ if (clientSecret && !await verifyStoredClientSecret(ctx, options, client.clientSecret, clientSecret)) throw new APIError$1("UNAUTHORIZED", {
247
+ error_description: "invalid client_secret",
248
+ error: "invalid_client"
249
+ });
250
+ if (scopes && client.scopes) {
251
+ const validScopes = new Set(client.scopes);
252
+ for (const sc of scopes) if (!validScopes.has(sc)) throw new APIError$1("BAD_REQUEST", {
253
+ error_description: `client does not allow scope ${sc}`,
254
+ error: "invalid_scope"
255
+ });
256
+ }
257
+ return client;
258
+ }
259
+ /**
260
+ * Parse client metadata that may be stored as JSON string or already parsed object.
261
+ * Handles database adapters that auto-parse JSON columns.
262
+ *
263
+ * @internal
264
+ */
265
+ function parseClientMetadata(metadata) {
266
+ if (!metadata) return void 0;
267
+ return typeof metadata === "string" ? JSON.parse(metadata) : metadata;
268
+ }
269
+ /**
270
+ * Parse space-separated prompt string into a set of prompts
271
+ *
272
+ * @param prompt
273
+ */
274
+ function parsePrompt(prompt) {
275
+ const prompts = prompt.split(" ").map((p) => p.trim());
276
+ const set = /* @__PURE__ */ new Set();
277
+ for (const p of prompts) if (p === "login" || p === "consent" || p === "create" || p === "select_account" || p === "none") set.add(p);
278
+ return new Set(set);
279
+ }
280
+ /**
281
+ * Deletes a prompt value
282
+ *
283
+ * @param ctx
284
+ * @param prompt - the prompt value to delete
285
+ */
286
+ function deleteFromPrompt(query, prompt) {
287
+ const prompts = query.get("prompt")?.split(" ");
288
+ const foundPrompt = prompts?.findIndex((v) => v === prompt) ?? -1;
289
+ if (foundPrompt >= 0) {
290
+ prompts?.splice(foundPrompt, 1);
291
+ prompts?.length ? query.set("prompt", prompts.join(" ")) : query.delete("prompt");
292
+ }
293
+ return Object.fromEntries(query);
294
+ }
295
+
296
+ //#endregion
297
+ export { getJwtPlugin as a, parseClientMetadata as c, storeToken as d, validateClientCredentials as f, getClient as i, parsePrompt as l, mcpHandler as m, decryptStoredClientSecret as n, getOAuthProviderPlugin as o, handleMcpErrors as p, deleteFromPrompt as r, getStoredToken as s, basicToClientCredentials as t, storeClientSecret as u };
298
+ //# sourceMappingURL=utils-BSruxDcm.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils-BSruxDcm.mjs","names":["APIError","APIError"],"sources":["../src/mcp.ts","../src/utils/index.ts"],"sourcesContent":["import { isAPIError } from \"better-auth/api\";\nimport { verifyAccessToken } from \"better-auth/oauth2\";\nimport { APIError } from \"better-call\";\nimport type { JWTPayload } from \"jose\";\nimport type { Awaitable } from \"./types/helpers\";\n\n/**\n * A request middleware handler that checks and responds with\n * a WWW-Authenticate header for unauthenticated responses.\n *\n * @external\n */\nexport const mcpHandler = (\n\t/** Resource is the same url as the audience */\n\tverifyOptions: Parameters<typeof verifyAccessToken>[1],\n\thandler: (req: Request, jwt: JWTPayload) => Awaitable<Response>,\n\topts?: {\n\t\t/** Maps non-url (ie urn, client) resources to resource_metadata */\n\t\tresourceMetadataMappings: Record<string, string>;\n\t},\n) => {\n\treturn async (req: Request) => {\n\t\tconst authorization = req.headers?.get(\"authorization\") ?? undefined;\n\t\tconst accessToken = authorization?.startsWith(\"Bearer \")\n\t\t\t? authorization.replace(\"Bearer \", \"\")\n\t\t\t: authorization;\n\t\ttry {\n\t\t\tif (!accessToken?.length) {\n\t\t\t\tthrow new APIError(\"UNAUTHORIZED\", {\n\t\t\t\t\tmessage: \"missing authorization header\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst token = await verifyAccessToken(accessToken, verifyOptions);\n\t\t\treturn handler(req, token);\n\t\t} catch (error) {\n\t\t\ttry {\n\t\t\t\thandleMcpErrors(error, verifyOptions.verifyOptions.audience, opts);\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof APIError) {\n\t\t\t\t\treturn new Response(err.message, {\n\t\t\t\t\t\t...err,\n\t\t\t\t\t\tstatus: err.statusCode,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tthrow new Error(String(err));\n\t\t\t}\n\t\t\tthrow new Error(String(error));\n\t\t}\n\t};\n};\n\n/**\n * The following handles all MCP errors and API errors\n *\n * @internal\n */\nexport function handleMcpErrors(\n\terror: unknown,\n\tresource: string | string[],\n\topts?: {\n\t\t/** Maps non-url (ie urn, client) resources to resource_metadata */\n\t\tresourceMetadataMappings?: Record<string, string>;\n\t},\n) {\n\tif (isAPIError(error) && error.status === \"UNAUTHORIZED\") {\n\t\tconst _resources = Array.isArray(resource) ? resource : [resource];\n\t\tconst wwwAuthenticateValue = _resources\n\t\t\t.map((v) => {\n\t\t\t\tlet audiencePath: string;\n\t\t\t\tif (URL.canParse?.(v)) {\n\t\t\t\t\tconst url = new URL(v);\n\t\t\t\t\taudiencePath = url.pathname.endsWith(\"/\")\n\t\t\t\t\t\t? url.pathname.slice(0, -1)\n\t\t\t\t\t\t: url.pathname;\n\t\t\t\t\treturn `Bearer resource_metadata=\"${url.origin}/.well-known/oauth-protected-resource${\n\t\t\t\t\t\taudiencePath\n\t\t\t\t\t}\"`;\n\t\t\t\t} else {\n\t\t\t\t\tconst resourceMetadata = opts?.resourceMetadataMappings?.[v];\n\t\t\t\t\tif (!resourceMetadata) {\n\t\t\t\t\t\tthrow new APIError(\"INTERNAL_SERVER_ERROR\", {\n\t\t\t\t\t\t\tmessage: `missing resource_metadata mapping for ${v}`,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\treturn `Bearer resource_metadata=${resourceMetadata}`;\n\t\t\t\t}\n\t\t\t})\n\t\t\t.join(\", \");\n\t\tthrow new APIError(\n\t\t\t\"UNAUTHORIZED\",\n\t\t\t{\n\t\t\t\tmessage: error.message,\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"WWW-Authenticate\": wwwAuthenticateValue,\n\t\t\t},\n\t\t);\n\t} else if (error instanceof Error) {\n\t\tthrow error;\n\t} else {\n\t\tthrow new Error(error as unknown as string);\n\t}\n}\n","import type { AuthContext, GenericEndpointContext } from \"@better-auth/core\";\nimport { BetterAuthError } from \"@better-auth/core/error\";\nimport { base64, base64Url } from \"@better-auth/utils/base64\";\nimport { createHash } from \"@better-auth/utils/hash\";\nimport {\n\tconstantTimeEqual,\n\tsymmetricDecrypt,\n\tsymmetricEncrypt,\n} from \"better-auth/crypto\";\nimport type { jwt } from \"better-auth/plugins\";\nimport { APIError } from \"better-call\";\nimport type { oauthProvider } from \"../oauth\";\nimport type {\n\tOAuthOptions,\n\tPrompt,\n\tSchemaClient,\n\tScope,\n\tStoreTokenType,\n} from \"../types\";\n\nclass TTLCache<K, V extends { expiresAt?: Date }> {\n\tprivate cache = new Map<K, V>();\n\tconstructor() {}\n\n\tset(key: K, value: V) {\n\t\tthis.cache.set(key, value);\n\t}\n\n\tget(key: K): V | undefined {\n\t\tconst entry = this.cache.get(key);\n\t\tif (!entry) return undefined;\n\t\tif (entry.expiresAt && entry.expiresAt < new Date()) {\n\t\t\tthis.cache.delete(key);\n\t\t\treturn undefined;\n\t\t}\n\t\treturn entry;\n\t}\n}\n\n/**\n * Gets the oAuth Provider Plugin\n * @internal\n */\nexport const getOAuthProviderPlugin = (ctx: AuthContext) => {\n\treturn ctx.getPlugin(\"oauth-provider\") satisfies ReturnType<\n\t\ttypeof oauthProvider\n\t> | null;\n};\n\n/**\n * Gets the JWT Plugin\n * @internal\n */\nexport const getJwtPlugin = (ctx: AuthContext) => {\n\tconst plugin = ctx.getPlugin(\"jwt\") satisfies ReturnType<typeof jwt> | null;\n\tif (!plugin) {\n\t\tthrow new BetterAuthError(\"jwt_config\", \"jwt plugin not found\");\n\t}\n\treturn plugin;\n};\n\nconst cachedTrustedClients = new TTLCache<string, SchemaClient<Scope[]>>();\n\n/**\n * Get a client by ID, checking trusted clients first, then database\n */\nexport async function getClient(\n\tctx: GenericEndpointContext,\n\toptions: OAuthOptions<Scope[]>,\n\tclientId: string,\n) {\n\tconst trustedClient = cachedTrustedClients.get(clientId);\n\tif (trustedClient) {\n\t\treturn Object.assign({}, trustedClient);\n\t}\n\n\tconst dbClient = await ctx.context.adapter.findOne<SchemaClient<Scope[]>>({\n\t\tmodel: options.schema?.oauthClient?.modelName ?? \"oauthClient\",\n\t\twhere: [{ field: \"clientId\", value: clientId }],\n\t});\n\n\tif (dbClient && options.cachedTrustedClients?.has(clientId)) {\n\t\tcachedTrustedClients.set(clientId, Object.assign({}, dbClient));\n\t}\n\n\treturn dbClient;\n}\n\n/**\n * Default client secret hasher using SHA-256\n *\n * @internal\n */\nconst defaultHasher = async (value: string) => {\n\tconst hash = await createHash(\"SHA-256\").digest(\n\t\tnew TextEncoder().encode(value),\n\t);\n\tconst hashed = base64Url.encode(new Uint8Array(hash), {\n\t\tpadding: false,\n\t});\n\treturn hashed;\n};\n\n/**\n * Decrypts a storedClientSecret for signing\n *\n * @internal\n */\nexport async function decryptStoredClientSecret(\n\tctx: GenericEndpointContext,\n\tstorageMethod: OAuthOptions<Scope[]>[\"storeClientSecret\"],\n\tstoredClientSecret: string,\n) {\n\tif (storageMethod === \"encrypted\") {\n\t\treturn await symmetricDecrypt({\n\t\t\tkey: ctx.context.secret,\n\t\t\tdata: storedClientSecret,\n\t\t});\n\t} else if (typeof storageMethod === \"object\" && \"decrypt\" in storageMethod) {\n\t\treturn await storageMethod.decrypt(storedClientSecret);\n\t}\n\n\tthrow new BetterAuthError(\n\t\t`Unsupported decryption storageMethod type '${storageMethod}'`,\n\t);\n}\n\n/**\n * Verify stored client secret against provided client secret\n *\n * @internal\n */\nasync function verifyStoredClientSecret(\n\tctx: GenericEndpointContext,\n\topts: OAuthOptions<Scope[]>,\n\tstoredClientSecret: string,\n\tclientSecret?: string,\n): Promise<boolean> {\n\tconst storageMethod =\n\t\topts.storeClientSecret ?? (opts.disableJwtPlugin ? \"encrypted\" : \"hashed\");\n\n\tif (clientSecret && opts.prefix?.clientSecret) {\n\t\tif (clientSecret.startsWith(opts.prefix?.clientSecret)) {\n\t\t\tclientSecret = clientSecret.replace(opts.prefix.clientSecret, \"\");\n\t\t} else {\n\t\t\tthrow new APIError(\"UNAUTHORIZED\", {\n\t\t\t\terror_description: \"invalid client_secret\",\n\t\t\t\terror: \"invalid_client\",\n\t\t\t});\n\t\t}\n\t}\n\n\tif (storageMethod === \"hashed\") {\n\t\tconst hashedClientSecret = clientSecret\n\t\t\t? await defaultHasher(clientSecret)\n\t\t\t: undefined;\n\t\treturn (\n\t\t\t!!hashedClientSecret &&\n\t\t\tconstantTimeEqual(hashedClientSecret, storedClientSecret)\n\t\t);\n\t} else if (typeof storageMethod === \"object\" && \"hash\" in storageMethod) {\n\t\tif (storageMethod.verify) {\n\t\t\treturn (\n\t\t\t\t!!clientSecret &&\n\t\t\t\t(await storageMethod.verify(clientSecret, storedClientSecret))\n\t\t\t);\n\t\t} else {\n\t\t\tconst hashedClientSecret = clientSecret\n\t\t\t\t? await storageMethod.hash(clientSecret)\n\t\t\t\t: undefined;\n\t\t\treturn (\n\t\t\t\t!!hashedClientSecret &&\n\t\t\t\tconstantTimeEqual(hashedClientSecret, storedClientSecret)\n\t\t\t);\n\t\t}\n\t} else if (\n\t\tstorageMethod === \"encrypted\" ||\n\t\t(typeof storageMethod === \"object\" && \"decrypt\" in storageMethod)\n\t) {\n\t\tconst decryptedClientSecret = await decryptStoredClientSecret(\n\t\t\tctx,\n\t\t\tstorageMethod,\n\t\t\tstoredClientSecret,\n\t\t);\n\t\treturn (\n\t\t\t!!clientSecret && constantTimeEqual(decryptedClientSecret, clientSecret)\n\t\t);\n\t}\n\n\tthrow new BetterAuthError(\n\t\t`Unsupported verify storageMethod type '${storageMethod}'`,\n\t);\n}\n\n/**\n * Store client secret according to the configured storage method\n *\n * @internal\n */\nexport async function storeClientSecret(\n\tctx: GenericEndpointContext,\n\topts: OAuthOptions<Scope[]>,\n\tclientSecret: string,\n) {\n\tconst storageMethod =\n\t\topts.storeClientSecret ?? (opts.disableJwtPlugin ? \"encrypted\" : \"hashed\");\n\n\tif (storageMethod === \"encrypted\") {\n\t\treturn await symmetricEncrypt({\n\t\t\tkey: ctx.context.secret,\n\t\t\tdata: clientSecret,\n\t\t});\n\t} else if (storageMethod === \"hashed\") {\n\t\treturn await defaultHasher(clientSecret);\n\t} else if (typeof storageMethod === \"object\" && \"hash\" in storageMethod) {\n\t\treturn await storageMethod.hash(clientSecret);\n\t} else if (typeof storageMethod === \"object\" && \"encrypt\" in storageMethod) {\n\t\treturn await storageMethod.encrypt(clientSecret);\n\t}\n\n\tthrow new BetterAuthError(\n\t\t`Unsupported storeClientSecret type '${storageMethod}'`,\n\t);\n}\n\n/**\n * Stores a token value (ie opaque tokens, refresh tokens, transaction tokens, verification codes)\n * on the database in a secure hashed format.\n *\n * @internal\n */\nexport async function storeToken(\n\tstorageMethod: OAuthOptions<Scope[]>[\"storeTokens\"] = \"hashed\",\n\ttoken: string,\n\ttype: StoreTokenType,\n) {\n\tif (storageMethod === \"hashed\") {\n\t\treturn await defaultHasher(token);\n\t} else if (typeof storageMethod === \"object\" && \"hash\" in storageMethod) {\n\t\treturn await storageMethod.hash(token, type);\n\t}\n\n\tthrow new BetterAuthError(\n\t\t`storeToken: unsupported storageMethod type '${storageMethod}'`,\n\t);\n}\n\n/**\n * Gets a hashed token value to find on the database.\n *\n * @internal\n */\nexport async function getStoredToken(\n\tstorageMethod: OAuthOptions<Scope[]>[\"storeTokens\"] = \"hashed\",\n\ttoken: string,\n\ttype: StoreTokenType,\n) {\n\tif (storageMethod === \"hashed\") {\n\t\tconst hashedToken = await defaultHasher(token);\n\t\treturn hashedToken;\n\t} else if (typeof storageMethod === \"object\" && \"hash\" in storageMethod) {\n\t\tconst hashedToken = await storageMethod.hash(token, type);\n\t\treturn hashedToken;\n\t}\n\n\tthrow new BetterAuthError(\n\t\t`getStoredToken: unsupported storageMethod type '${storageMethod}'`,\n\t);\n}\n\n/**\n * Converts a BASIC authorization header\n * into its client_id and client_secret representation\n *\n * @internal\n */\nexport function basicToClientCredentials(authorization: string) {\n\tif (authorization.startsWith(\"Basic \")) {\n\t\tconst encoded = authorization.replace(\"Basic \", \"\");\n\t\tconst decoded = new TextDecoder().decode(base64.decode(encoded));\n\t\tif (!decoded.includes(\":\")) {\n\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\terror_description: \"invalid authorization header format\",\n\t\t\t\terror: \"invalid_client\",\n\t\t\t});\n\t\t}\n\t\tconst [id, secret] = decoded.split(\":\", 2);\n\t\tif (!id || !secret) {\n\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\terror_description: \"invalid authorization header format\",\n\t\t\t\terror: \"invalid_client\",\n\t\t\t});\n\t\t}\n\t\treturn {\n\t\t\tclient_id: id,\n\t\t\tclient_secret: secret,\n\t\t};\n\t}\n}\n\n/**\n * Validates client credentials failing on mismatches\n * and incorrectly provided information\n *\n * @internal\n */\nexport async function validateClientCredentials(\n\tctx: GenericEndpointContext,\n\toptions: OAuthOptions<Scope[]>,\n\tclientId: string,\n\tclientSecret?: string, // optional because required if client is confidential or this value is defined\n\tscopes?: string[], // checks requested scopes against allowed scopes\n) {\n\tconst client = await getClient(ctx, options, clientId);\n\tif (!client) {\n\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\terror_description: \"missing client\",\n\t\t\terror: \"invalid_client\",\n\t\t});\n\t}\n\tif (client.disabled) {\n\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\terror_description: \"client is disabled\",\n\t\t\terror: \"invalid_client\",\n\t\t});\n\t}\n\n\t// Require secret for confidential clients\n\tif (!client.public && !clientSecret) {\n\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\terror_description: \"client secret must be provided\",\n\t\t\terror: \"invalid_client\",\n\t\t});\n\t}\n\n\t// Secret should not be received\n\tif (clientSecret && !client.clientSecret) {\n\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\terror_description: \"public client, client secret should not be received\",\n\t\t\terror: \"invalid_client\",\n\t\t});\n\t}\n\n\t// Compare Secrets when secret is provided\n\tif (\n\t\tclientSecret &&\n\t\t!(await verifyStoredClientSecret(\n\t\t\tctx,\n\t\t\toptions,\n\t\t\tclient.clientSecret!,\n\t\t\tclientSecret,\n\t\t))\n\t) {\n\t\tthrow new APIError(\"UNAUTHORIZED\", {\n\t\t\terror_description: \"invalid client_secret\",\n\t\t\terror: \"invalid_client\",\n\t\t});\n\t}\n\n\t// If scopes set, check against client allowed scopes\n\tif (scopes && client.scopes) {\n\t\tconst validScopes = new Set(client.scopes);\n\t\tfor (const sc of scopes) {\n\t\t\tif (!validScopes.has(sc)) {\n\t\t\t\tthrow new APIError(\"BAD_REQUEST\", {\n\t\t\t\t\terror_description: `client does not allow scope ${sc}`,\n\t\t\t\t\terror: \"invalid_scope\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\treturn client;\n}\n\n/**\n * Parse client metadata that may be stored as JSON string or already parsed object.\n * Handles database adapters that auto-parse JSON columns.\n *\n * @internal\n */\nexport function parseClientMetadata(\n\tmetadata: string | object | undefined,\n): object | undefined {\n\tif (!metadata) return undefined;\n\treturn typeof metadata === \"string\" ? JSON.parse(metadata) : metadata;\n}\n\n/**\n * Parse space-separated prompt string into a set of prompts\n *\n * @param prompt\n */\nexport function parsePrompt(prompt: string) {\n\tconst prompts = prompt.split(\" \").map((p) => p.trim());\n\tconst set = new Set<Prompt>();\n\tfor (const p of prompts) {\n\t\tif (\n\t\t\tp === \"login\" ||\n\t\t\tp === \"consent\" ||\n\t\t\tp === \"create\" ||\n\t\t\tp === \"select_account\" ||\n\t\t\tp === \"none\"\n\t\t) {\n\t\t\tset.add(p);\n\t\t}\n\t}\n\treturn new Set(set);\n}\n\n/**\n * Deletes a prompt value\n *\n * @param ctx\n * @param prompt - the prompt value to delete\n */\nexport function deleteFromPrompt(query: URLSearchParams, prompt: Prompt) {\n\tconst prompts = query.get(\"prompt\")?.split(\" \");\n\tconst foundPrompt = prompts?.findIndex((v) => v === prompt) ?? -1;\n\tif (foundPrompt >= 0) {\n\t\tprompts?.splice(foundPrompt, 1);\n\t\tprompts?.length\n\t\t\t? query.set(\"prompt\", prompts.join(\" \"))\n\t\t\t: query.delete(\"prompt\");\n\t}\n\treturn Object.fromEntries(query);\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "@hammadj/better-auth-oauth-provider",
3
+ "version": "1.5.0-beta.9",
4
+ "type": "module",
5
+ "description": "An oauth provider plugin for Better Auth",
6
+ "main": "dist/index.mjs",
7
+ "module": "dist/index.mjs",
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "dev-source": "./src/index.ts",
14
+ "types": "./dist/index.d.mts",
15
+ "default": "./dist/index.mjs"
16
+ },
17
+ "./client": {
18
+ "dev-source": "./src/client.ts",
19
+ "types": "./dist/client.d.mts",
20
+ "default": "./dist/client.mjs"
21
+ },
22
+ "./resource-client": {
23
+ "dev-source": "./src/client-resource.ts",
24
+ "types": "./dist/client-resource.d.mts",
25
+ "default": "./dist/client-resource.mjs"
26
+ }
27
+ },
28
+ "typesVersions": {
29
+ "*": {
30
+ "*": [
31
+ "./dist/index.d.mts"
32
+ ],
33
+ "client": [
34
+ "./dist/client.d.mts"
35
+ ],
36
+ "resource-client": [
37
+ "./dist/client-resource.d.mts"
38
+ ]
39
+ }
40
+ },
41
+ "devDependencies": {
42
+ "@modelcontextprotocol/sdk": "^1.25.3",
43
+ "listhen": "^1.9.0",
44
+ "tsdown": "^0.20.1",
45
+ "@hammadj/better-auth-core": "1.5.0-beta.9",
46
+ "@hammadj/better-auth": "1.5.0-beta.9"
47
+ },
48
+ "dependencies": {
49
+ "jose": "^6.1.0",
50
+ "zod": "^4.3.6"
51
+ },
52
+ "peerDependencies": {
53
+ "@better-auth/utils": "0.3.1",
54
+ "@better-fetch/fetch": "1.1.21",
55
+ "better-call": "1.2.0",
56
+ "@hammadj/better-auth-core": "1.5.0-beta.9",
57
+ "@hammadj/better-auth": "1.5.0-beta.9"
58
+ },
59
+ "files": [
60
+ "dist"
61
+ ],
62
+ "repository": {
63
+ "type": "git",
64
+ "url": "git+https://github.com/META-DREAMER/better-auth.git",
65
+ "directory": "packages/oauth-provider"
66
+ },
67
+ "homepage": "https://www.better-auth.com/docs/plugins/oauth-provider",
68
+ "keywords": [
69
+ "auth",
70
+ "oauth",
71
+ "typescript",
72
+ "better-auth"
73
+ ],
74
+ "license": "MIT",
75
+ "scripts": {
76
+ "test": "vitest",
77
+ "coverage": "vitest run --coverage --coverage.provider=istanbul",
78
+ "lint:package": "publint run --strict",
79
+ "build": "tsdown",
80
+ "dev": "tsdown --watch",
81
+ "typecheck": "tsc --project tsconfig.json"
82
+ }
83
+ }