better-auth 0.0.2-beta.7 → 0.0.2
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/dist/access.d.ts +4 -0
- package/dist/access.js +126 -0
- package/dist/access.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +553 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/plugins.d.ts +2436 -0
- package/dist/client/plugins.js +411 -0
- package/dist/client/plugins.js.map +1 -0
- package/dist/client-A2Mt04KQ.d.ts +3503 -0
- package/dist/client.d.ts +1433 -0
- package/dist/client.js +693 -0
- package/dist/client.js.map +1 -0
- package/dist/helper-B5_2Vzba.d.ts +14 -0
- package/dist/index-Dg4eEXZW.d.ts +24 -0
- package/dist/index-W5nXvJ-p.d.ts +1498 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +2195 -1191
- package/dist/index.js.map +1 -1
- package/dist/next-js.d.ts +14 -0
- package/dist/next-js.js +14 -0
- package/dist/next-js.js.map +1 -0
- package/dist/plugins.d.ts +892 -49
- package/dist/plugins.js +3951 -253
- package/dist/plugins.js.map +1 -1
- package/dist/preact.d.ts +8 -0
- package/dist/preact.js +294 -0
- package/dist/preact.js.map +1 -0
- package/dist/react.d.ts +14 -0
- package/dist/react.js +314 -0
- package/dist/react.js.map +1 -0
- package/dist/schema-BOszzrbQ.d.ts +792 -0
- package/dist/social.d.ts +4 -0
- package/dist/social.js +509 -0
- package/dist/social.js.map +1 -0
- package/dist/solid-start.d.ts +18 -0
- package/dist/solid-start.js +14 -0
- package/dist/solid-start.js.map +1 -0
- package/dist/solid.d.ts +2790 -0
- package/dist/solid.js +306 -0
- package/dist/solid.js.map +1 -0
- package/dist/statement-COylZd3J.d.ts +81 -0
- package/dist/svelte-kit.d.ts +10 -7
- package/dist/svelte-kit.js +12 -17
- package/dist/svelte-kit.js.map +1 -1
- package/dist/svelte.d.ts +2791 -0
- package/dist/svelte.js +304 -0
- package/dist/svelte.js.map +1 -0
- package/dist/type-DbMyI3b5.d.ts +5724 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/dist/vue.d.ts +14 -0
- package/dist/vue.js +311 -0
- package/dist/vue.js.map +1 -0
- package/package.json +80 -54
- package/LICENSE +0 -21
- package/dist/actions.d.ts +0 -33
- package/dist/actions.js +0 -1373
- package/dist/actions.js.map +0 -1
- package/dist/adapters/drizzle-adapter.d.ts +0 -10
- package/dist/adapters/drizzle-adapter.js +0 -1095
- package/dist/adapters/drizzle-adapter.js.map +0 -1
- package/dist/adapters/memory.d.ts +0 -8
- package/dist/adapters/memory.js +0 -136
- package/dist/adapters/memory.js.map +0 -1
- package/dist/adapters/mongodb-adapter.d.ts +0 -9
- package/dist/adapters/mongodb-adapter.js +0 -97
- package/dist/adapters/mongodb-adapter.js.map +0 -1
- package/dist/adapters/prisma-adapter.d.ts +0 -7
- package/dist/adapters/prisma-adapter.js +0 -144
- package/dist/adapters/prisma-adapter.js.map +0 -1
- package/dist/adapters/redis-adapter.d.ts +0 -7
- package/dist/adapters/redis-adapter.js +0 -65
- package/dist/adapters/redis-adapter.js.map +0 -1
- package/dist/adapters.d.ts +0 -3
- package/dist/adapters.js +0 -206
- package/dist/adapters.js.map +0 -1
- package/dist/h3.d.ts +0 -10
- package/dist/h3.js +0 -326
- package/dist/h3.js.map +0 -1
- package/dist/hono.d.ts +0 -10
- package/dist/hono.js +0 -25
- package/dist/hono.js.map +0 -1
- package/dist/index-UcTu1vUg.d.ts +0 -107
- package/dist/next.d.ts +0 -17
- package/dist/next.js +0 -26
- package/dist/next.js.map +0 -1
- package/dist/options-CH15FEBw.d.ts +0 -1562
- package/dist/providers.d.ts +0 -3
- package/dist/providers.js +0 -653
- package/dist/providers.js.map +0 -1
- package/dist/routes/session.d.ts +0 -39
- package/dist/routes/session.js +0 -128
- package/dist/routes/session.js.map +0 -1
- package/dist/types-DAxaMWCy.d.ts +0 -136
package/dist/index.js
CHANGED
|
@@ -1,81 +1,90 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
1
|
+
// src/api/index.ts
|
|
2
|
+
import { createRouter } from "better-call";
|
|
3
|
+
|
|
4
|
+
// src/adapters/schema.ts
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
var accountSchema = z.object({
|
|
7
|
+
id: z.string(),
|
|
8
|
+
providerId: z.string(),
|
|
9
|
+
accountId: z.string(),
|
|
10
|
+
userId: z.string(),
|
|
11
|
+
accessToken: z.string().nullable().optional(),
|
|
12
|
+
refreshToken: z.string().nullable().optional(),
|
|
13
|
+
idToken: z.string().nullable().optional(),
|
|
14
|
+
accessTokenExpiresAt: z.date().nullable().optional(),
|
|
15
|
+
refreshTokenExpiresAt: z.date().nullable().optional(),
|
|
16
|
+
/**
|
|
17
|
+
* Password is only stored in the credential provider
|
|
18
|
+
*/
|
|
19
|
+
password: z.string().optional().nullable()
|
|
20
|
+
});
|
|
21
|
+
var userSchema = z.object({
|
|
22
|
+
id: z.string(),
|
|
23
|
+
email: z.string().transform((val) => val.toLowerCase()),
|
|
24
|
+
emailVerified: z.boolean().default(false),
|
|
25
|
+
name: z.string(),
|
|
26
|
+
image: z.string().optional(),
|
|
27
|
+
createdAt: z.date().default(/* @__PURE__ */ new Date()),
|
|
28
|
+
updatedAt: z.date().default(/* @__PURE__ */ new Date())
|
|
29
|
+
});
|
|
30
|
+
var sessionSchema = z.object({
|
|
31
|
+
id: z.string(),
|
|
32
|
+
userId: z.string(),
|
|
33
|
+
expiresAt: z.date(),
|
|
34
|
+
ipAddress: z.string().optional(),
|
|
35
|
+
userAgent: z.string().optional()
|
|
36
|
+
});
|
|
37
|
+
function parseData(data, schema) {
|
|
38
|
+
const fields = schema.fields;
|
|
39
|
+
const parsedData = {};
|
|
40
|
+
for (const key in data) {
|
|
41
|
+
const field = fields[key];
|
|
42
|
+
if (!field) {
|
|
43
|
+
parsedData[key] = data[key];
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (field.returned === false) {
|
|
42
47
|
continue;
|
|
43
|
-
|
|
48
|
+
}
|
|
49
|
+
parsedData[key] = data[key];
|
|
44
50
|
}
|
|
45
|
-
return
|
|
51
|
+
return parsedData;
|
|
46
52
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (!cookie)
|
|
56
|
-
return null;
|
|
57
|
-
const cookies = parse(cookie);
|
|
58
|
-
const value = cookies.get(name);
|
|
59
|
-
return value;
|
|
53
|
+
function getAllFields(options, table) {
|
|
54
|
+
let schema = {};
|
|
55
|
+
for (const plugin of options.plugins || []) {
|
|
56
|
+
if (plugin.schema && plugin.schema[table]) {
|
|
57
|
+
schema = {
|
|
58
|
+
...schema,
|
|
59
|
+
...plugin.schema[table].fields
|
|
60
|
+
};
|
|
60
61
|
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function setSessionCookie(context, sessionId) {
|
|
64
|
-
context.request.cookies.set(
|
|
65
|
-
context.cookies.sessionToken.name,
|
|
66
|
-
sessionId,
|
|
67
|
-
context.cookies.sessionToken.options
|
|
68
|
-
);
|
|
62
|
+
}
|
|
63
|
+
return schema;
|
|
69
64
|
}
|
|
70
|
-
function
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
maxAge: 0
|
|
74
|
-
});
|
|
65
|
+
function parseUser(options, user) {
|
|
66
|
+
const schema = getAllFields(options, "user");
|
|
67
|
+
return parseData(user, { fields: schema });
|
|
75
68
|
}
|
|
69
|
+
function parseAccount(options, account) {
|
|
70
|
+
const schema = getAllFields(options, "account");
|
|
71
|
+
return parseData(account, { fields: schema });
|
|
72
|
+
}
|
|
73
|
+
function parseSession(options, session) {
|
|
74
|
+
const schema = getAllFields(options, "session");
|
|
75
|
+
return parseData(session, { fields: schema });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/api/middlewares/csrf.ts
|
|
79
|
+
import { APIError } from "better-call";
|
|
80
|
+
import { z as z2 } from "zod";
|
|
76
81
|
|
|
77
|
-
// src/crypto/
|
|
78
|
-
|
|
82
|
+
// src/crypto/index.ts
|
|
83
|
+
import { xchacha20poly1305 } from "@noble/ciphers/chacha";
|
|
84
|
+
import { bytesToHex, hexToBytes, utf8ToBytes } from "@noble/ciphers/utils";
|
|
85
|
+
import { managedNonce } from "@noble/ciphers/webcrypto";
|
|
86
|
+
import { sha256 } from "@noble/hashes/sha256";
|
|
87
|
+
async function hs256(secretKey, message) {
|
|
79
88
|
const enc = new TextEncoder();
|
|
80
89
|
const algorithm = { name: "HMAC", hash: "SHA-256" };
|
|
81
90
|
const key = await crypto.subtle.importKey(
|
|
@@ -93,638 +102,2089 @@ async function hmac(secretKey, message) {
|
|
|
93
102
|
return btoa(String.fromCharCode(...new Uint8Array(signature)));
|
|
94
103
|
}
|
|
95
104
|
|
|
96
|
-
// src/
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
// src/api/call.ts
|
|
106
|
+
import {
|
|
107
|
+
createEndpointCreator,
|
|
108
|
+
createMiddleware,
|
|
109
|
+
createMiddlewareCreator
|
|
110
|
+
} from "better-call";
|
|
111
|
+
var optionsMiddleware = createMiddleware(async () => {
|
|
112
|
+
return {};
|
|
113
|
+
});
|
|
114
|
+
var createAuthMiddleware = createMiddlewareCreator({
|
|
115
|
+
use: [optionsMiddleware]
|
|
116
|
+
});
|
|
117
|
+
var createAuthEndpoint = createEndpointCreator({
|
|
118
|
+
use: [optionsMiddleware]
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// src/api/middlewares/csrf.ts
|
|
122
|
+
var csrfMiddleware = createAuthMiddleware(
|
|
123
|
+
{
|
|
124
|
+
body: z2.object({
|
|
125
|
+
csrfToken: z2.string().optional()
|
|
126
|
+
}).optional()
|
|
127
|
+
},
|
|
128
|
+
async (ctx) => {
|
|
129
|
+
if (ctx.request?.method !== "POST" || ctx.context.options.advanced?.disableCSRFCheck) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const url = new URL(ctx.request.url);
|
|
133
|
+
console.log(url.origin, ctx.context.options.baseURL);
|
|
134
|
+
if (url.origin === ctx.context.options.baseURL || ctx.context.options.trustedOrigins?.includes(url.origin)) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const csrfToken = ctx.body?.csrfToken;
|
|
138
|
+
const csrfCookie = await ctx.getSignedCookie(
|
|
139
|
+
ctx.context.authCookies.csrfToken.name,
|
|
140
|
+
ctx.context.secret
|
|
141
|
+
);
|
|
142
|
+
const [token, hash] = csrfCookie?.split("!") || [null, null];
|
|
143
|
+
if (!csrfToken || !csrfCookie || !token || !hash || csrfCookie !== csrfToken) {
|
|
144
|
+
ctx.setCookie(ctx.context.authCookies.csrfToken.name, "", {
|
|
145
|
+
maxAge: 0
|
|
146
|
+
});
|
|
147
|
+
throw new APIError("UNAUTHORIZED", {
|
|
148
|
+
message: "Invalid CSRF Token"
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const expectedHash = await hs256(ctx.context.secret, token);
|
|
152
|
+
if (hash !== expectedHash) {
|
|
153
|
+
ctx.setCookie(ctx.context.authCookies.csrfToken.name, "", {
|
|
154
|
+
maxAge: 0
|
|
155
|
+
});
|
|
156
|
+
throw new APIError("UNAUTHORIZED", {
|
|
157
|
+
message: "Invalid CSRF Token"
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// src/api/routes/sign-in.ts
|
|
164
|
+
import { APIError as APIError2 } from "better-call";
|
|
165
|
+
import { generateCodeVerifier } from "oslo/oauth2";
|
|
166
|
+
import { Argon2id } from "oslo/password";
|
|
167
|
+
import { z as z3 } from "zod";
|
|
168
|
+
|
|
169
|
+
// src/social-providers/apple.ts
|
|
170
|
+
import "arctic";
|
|
171
|
+
import { parseJWT } from "oslo/jwt";
|
|
172
|
+
import { betterFetch } from "@better-fetch/fetch";
|
|
173
|
+
|
|
174
|
+
// src/error/better-auth-error.ts
|
|
175
|
+
var BetterAuthError = class extends Error {
|
|
176
|
+
constructor(message) {
|
|
177
|
+
super(message);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
103
180
|
|
|
104
|
-
// src/
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
181
|
+
// src/utils/base-url.ts
|
|
182
|
+
function checkHasPath(url) {
|
|
183
|
+
try {
|
|
184
|
+
const parsedUrl = new URL(url);
|
|
185
|
+
return parsedUrl.pathname !== "/";
|
|
186
|
+
} catch (error2) {
|
|
187
|
+
console.error("Invalid URL:", error2);
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function withPath(url, path = "/api/auth") {
|
|
192
|
+
const hasPath = checkHasPath(url);
|
|
193
|
+
if (hasPath) {
|
|
108
194
|
return {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
195
|
+
baseURL: new URL(url).origin,
|
|
196
|
+
withPath: url
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
path = path.startsWith("/") ? path : `/${path}`;
|
|
200
|
+
return {
|
|
201
|
+
baseURL: url,
|
|
202
|
+
withPath: `${url}${path}`
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function getBaseURL(url, path) {
|
|
206
|
+
if (url) {
|
|
207
|
+
return withPath(url, path);
|
|
208
|
+
}
|
|
209
|
+
const env = typeof process !== "undefined" ? process.env : typeof import.meta !== "undefined" ? (
|
|
210
|
+
//@ts-ignore
|
|
211
|
+
import.meta.env
|
|
212
|
+
) : {};
|
|
213
|
+
const fromEnv = env.BETTER_AUTH_URL || env.AUTH_URL || env.NEXT_PUBLIC_AUTH_URL || env.NEXT_PUBLIC_BETTER_AUTH_URL || env.PUBLIC_AUTH_URL || env.PUBLIC_BETTER_AUTH_URL || env.NUXT_PUBLIC_BETTER_AUTH_URL || env.NUXT_PUBLIC_AUTH_URL;
|
|
214
|
+
if (fromEnv) {
|
|
215
|
+
return withPath(fromEnv, path);
|
|
216
|
+
}
|
|
217
|
+
const isDev = !fromEnv && (env.NODE_ENV === "development" || env.NODE_ENV === "test");
|
|
218
|
+
if (isDev) {
|
|
219
|
+
return {
|
|
220
|
+
baseURL: "http://localhost:3000",
|
|
221
|
+
withPath: "http://localhost:3000/api/auth"
|
|
113
222
|
};
|
|
114
223
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const cookie = `${token}!${hash}`;
|
|
118
|
-
context.request.cookies.set(
|
|
119
|
-
context.cookies.csrfToken.name,
|
|
120
|
-
cookie,
|
|
121
|
-
context.cookies.csrfToken.options
|
|
224
|
+
throw new BetterAuthError(
|
|
225
|
+
"Could not infer baseURL from environment variables"
|
|
122
226
|
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/social-providers/utils.ts
|
|
230
|
+
function getRedirectURI(providerId, redirectURI) {
|
|
231
|
+
return redirectURI || `${getBaseURL()}/api/auth/callback/${providerId}`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/social-providers/apple.ts
|
|
235
|
+
var apple = ({
|
|
236
|
+
clientId,
|
|
237
|
+
clientSecret,
|
|
238
|
+
redirectURI
|
|
239
|
+
}) => {
|
|
240
|
+
const tokenEndpoint = "https://appleid.apple.com/auth/token";
|
|
241
|
+
redirectURI = getRedirectURI("apple", redirectURI);
|
|
123
242
|
return {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
243
|
+
id: "apple",
|
|
244
|
+
name: "Apple",
|
|
245
|
+
createAuthorizationURL({ state, scopes }) {
|
|
246
|
+
const _scope = scopes || ["email", "name", "openid"];
|
|
247
|
+
return new URL(
|
|
248
|
+
`https://appleid.apple.com/auth/authorize?client_id=${clientId}&response_type=code&redirect_uri=${redirectURI}&scope=${_scope.join(
|
|
249
|
+
" "
|
|
250
|
+
)}&state=${state}`
|
|
251
|
+
);
|
|
252
|
+
},
|
|
253
|
+
validateAuthorizationCode: async (code) => {
|
|
254
|
+
const data = await betterFetch(tokenEndpoint, {
|
|
255
|
+
method: "POST",
|
|
256
|
+
body: new URLSearchParams({
|
|
257
|
+
client_id: clientId,
|
|
258
|
+
client_secret: clientSecret,
|
|
259
|
+
grant_type: "authorization_code",
|
|
260
|
+
code
|
|
261
|
+
}),
|
|
262
|
+
headers: {
|
|
263
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
if (data.error) {
|
|
267
|
+
throw new BetterAuthError(data.error?.message || "");
|
|
268
|
+
}
|
|
269
|
+
return data.data;
|
|
270
|
+
},
|
|
271
|
+
async getUserInfo(token) {
|
|
272
|
+
const data = parseJWT(token.idToken())?.payload;
|
|
273
|
+
if (!data) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
user: {
|
|
278
|
+
id: data.sub,
|
|
279
|
+
name: data.name,
|
|
280
|
+
email: data.email,
|
|
281
|
+
emailVerified: data.email_verified === "true"
|
|
282
|
+
},
|
|
283
|
+
data
|
|
284
|
+
};
|
|
127
285
|
}
|
|
128
286
|
};
|
|
129
287
|
};
|
|
130
|
-
|
|
288
|
+
|
|
289
|
+
// src/social-providers/discord.ts
|
|
290
|
+
import { betterFetch as betterFetch2 } from "@better-fetch/fetch";
|
|
291
|
+
import { Discord } from "arctic";
|
|
292
|
+
var discord = ({
|
|
293
|
+
clientId,
|
|
294
|
+
clientSecret,
|
|
295
|
+
redirectURI
|
|
296
|
+
}) => {
|
|
297
|
+
const discordArctic = new Discord(
|
|
298
|
+
clientId,
|
|
299
|
+
clientSecret,
|
|
300
|
+
getRedirectURI("discord", redirectURI)
|
|
301
|
+
);
|
|
131
302
|
return {
|
|
132
|
-
id: "
|
|
133
|
-
name: "
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
});
|
|
148
|
-
return {
|
|
149
|
-
response: {
|
|
150
|
-
status: 403,
|
|
151
|
-
statusText: "Invalid CSRF Token"
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
const expectedHash = await hmac(context.secret, token);
|
|
156
|
-
if (hash !== expectedHash) {
|
|
157
|
-
context.request.cookies.set(context.cookies.csrfToken.name, "", {
|
|
158
|
-
...context.cookies.csrfToken.options,
|
|
159
|
-
maxAge: 0
|
|
160
|
-
});
|
|
161
|
-
return {
|
|
162
|
-
response: {
|
|
163
|
-
status: 403,
|
|
164
|
-
statusText: "Invalid CSRF Token"
|
|
165
|
-
}
|
|
166
|
-
};
|
|
303
|
+
id: "discord",
|
|
304
|
+
name: "Discord",
|
|
305
|
+
createAuthorizationURL({ state, scopes }) {
|
|
306
|
+
const _scope = scopes || ["email"];
|
|
307
|
+
return discordArctic.createAuthorizationURL(state, _scope);
|
|
308
|
+
},
|
|
309
|
+
validateAuthorizationCode: discordArctic.validateAuthorizationCode,
|
|
310
|
+
async getUserInfo(token) {
|
|
311
|
+
const { data: profile, error: error2 } = await betterFetch2(
|
|
312
|
+
"https://discord.com/api/users/@me",
|
|
313
|
+
{
|
|
314
|
+
auth: {
|
|
315
|
+
type: "Bearer",
|
|
316
|
+
token: token.accessToken
|
|
317
|
+
}
|
|
167
318
|
}
|
|
319
|
+
);
|
|
320
|
+
if (error2) {
|
|
168
321
|
return null;
|
|
169
322
|
}
|
|
170
|
-
|
|
171
|
-
|
|
323
|
+
return {
|
|
324
|
+
user: {
|
|
325
|
+
id: profile.id,
|
|
326
|
+
name: profile.display_name || profile.username || "",
|
|
327
|
+
email: profile.email,
|
|
328
|
+
emailVerified: profile.verified
|
|
329
|
+
},
|
|
330
|
+
data: profile
|
|
331
|
+
};
|
|
332
|
+
}
|
|
172
333
|
};
|
|
173
334
|
};
|
|
174
335
|
|
|
175
|
-
// src/
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
...plugins.pre,
|
|
188
|
-
...plugins.unordered,
|
|
189
|
-
...internalPlugins,
|
|
190
|
-
...plugins.post
|
|
191
|
-
];
|
|
192
|
-
};
|
|
193
|
-
var usePlugins = (context, ignorePlugins) => {
|
|
194
|
-
const plugins = context.plugins.filter(
|
|
195
|
-
(pl) => pl.hooks && !ignorePlugins?.includes(pl.id)
|
|
336
|
+
// src/social-providers/facebook.ts
|
|
337
|
+
import { betterFetch as betterFetch3 } from "@better-fetch/fetch";
|
|
338
|
+
import { Facebook } from "arctic";
|
|
339
|
+
var facebook = ({
|
|
340
|
+
clientId,
|
|
341
|
+
clientSecret,
|
|
342
|
+
redirectURI
|
|
343
|
+
}) => {
|
|
344
|
+
const facebookArctic = new Facebook(
|
|
345
|
+
clientId,
|
|
346
|
+
clientSecret,
|
|
347
|
+
getRedirectURI("facebook", redirectURI)
|
|
196
348
|
);
|
|
197
|
-
const hooks = plugins.map((plugin) => {
|
|
198
|
-
return plugin.hooks;
|
|
199
|
-
});
|
|
200
|
-
const before = [];
|
|
201
|
-
const after = [];
|
|
202
|
-
for (const hook of hooks) {
|
|
203
|
-
if (hook.matcher(context)) {
|
|
204
|
-
hook.before && before.push(hook.before);
|
|
205
|
-
hook.after && after.push(hook.after);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
349
|
return {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
350
|
+
id: "facebook",
|
|
351
|
+
name: "Facebook",
|
|
352
|
+
createAuthorizationURL({ state, scopes }) {
|
|
353
|
+
const _scopes = scopes || ["email", "public_profile"];
|
|
354
|
+
return facebookArctic.createAuthorizationURL(state, _scopes);
|
|
355
|
+
},
|
|
356
|
+
validateAuthorizationCode: facebookArctic.validateAuthorizationCode,
|
|
357
|
+
async getUserInfo(token) {
|
|
358
|
+
const { data: profile, error: error2 } = await betterFetch3(
|
|
359
|
+
"https://graph.facebook.com/me",
|
|
360
|
+
{
|
|
361
|
+
auth: {
|
|
362
|
+
type: "Bearer",
|
|
363
|
+
token: token.accessToken
|
|
364
|
+
}
|
|
220
365
|
}
|
|
366
|
+
);
|
|
367
|
+
if (error2) {
|
|
368
|
+
return null;
|
|
221
369
|
}
|
|
222
370
|
return {
|
|
223
|
-
|
|
224
|
-
|
|
371
|
+
user: {
|
|
372
|
+
id: profile.id,
|
|
373
|
+
name: profile.name,
|
|
374
|
+
email: profile.email,
|
|
375
|
+
emailVerified: profile.email_verified
|
|
376
|
+
},
|
|
377
|
+
data: profile
|
|
225
378
|
};
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// src/social-providers/github.ts
|
|
384
|
+
import { betterFetch as betterFetch4 } from "@better-fetch/fetch";
|
|
385
|
+
import { GitHub } from "arctic";
|
|
386
|
+
var github = ({
|
|
387
|
+
clientId,
|
|
388
|
+
clientSecret,
|
|
389
|
+
redirectURI
|
|
390
|
+
}) => {
|
|
391
|
+
const githubArctic = new GitHub(
|
|
392
|
+
clientId,
|
|
393
|
+
clientSecret,
|
|
394
|
+
getRedirectURI("github", redirectURI)
|
|
395
|
+
);
|
|
396
|
+
return {
|
|
397
|
+
id: "github",
|
|
398
|
+
name: "Github",
|
|
399
|
+
createAuthorizationURL({ state, scopes }) {
|
|
400
|
+
const _scopes = scopes || ["user:email"];
|
|
401
|
+
return githubArctic.createAuthorizationURL(state, _scopes);
|
|
226
402
|
},
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
403
|
+
validateAuthorizationCode: githubArctic.validateAuthorizationCode,
|
|
404
|
+
async getUserInfo(token) {
|
|
405
|
+
const { data: profile, error: error2 } = await betterFetch4(
|
|
406
|
+
"https://api.github.com/user",
|
|
407
|
+
{
|
|
408
|
+
method: "GET",
|
|
409
|
+
headers: {
|
|
410
|
+
Authorization: `Bearer ${token.accessToken}`
|
|
411
|
+
}
|
|
234
412
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
413
|
+
);
|
|
414
|
+
if (error2) {
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
let emailVerified = false;
|
|
418
|
+
if (!profile.email) {
|
|
419
|
+
const { data, error: error3 } = await betterFetch4("https://api.github.com/user/emails", {
|
|
420
|
+
headers: {
|
|
421
|
+
Authorization: `Bearer ${token.accessToken}`,
|
|
422
|
+
"User-Agent": "better-auth"
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
if (!error3) {
|
|
426
|
+
profile.email = (data.find((e) => e.primary) ?? data[0])?.email;
|
|
427
|
+
emailVerified = data.find((e) => e.email === profile.email)?.verified ?? false;
|
|
238
428
|
}
|
|
239
429
|
}
|
|
240
430
|
return {
|
|
241
|
-
|
|
242
|
-
|
|
431
|
+
user: {
|
|
432
|
+
id: profile.id,
|
|
433
|
+
name: profile.name,
|
|
434
|
+
email: profile.email,
|
|
435
|
+
image: profile.avatar_url,
|
|
436
|
+
emailVerified,
|
|
437
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
438
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
439
|
+
},
|
|
440
|
+
data: profile
|
|
243
441
|
};
|
|
244
442
|
}
|
|
245
443
|
};
|
|
246
444
|
};
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
445
|
+
|
|
446
|
+
// src/social-providers/google.ts
|
|
447
|
+
import { Google } from "arctic";
|
|
448
|
+
import { parseJWT as parseJWT2 } from "oslo/jwt";
|
|
449
|
+
var google = ({
|
|
450
|
+
clientId,
|
|
451
|
+
clientSecret,
|
|
452
|
+
redirectURI
|
|
453
|
+
}) => {
|
|
454
|
+
const googleArctic = new Google(
|
|
455
|
+
clientId,
|
|
456
|
+
clientSecret,
|
|
457
|
+
getRedirectURI("google", redirectURI)
|
|
458
|
+
);
|
|
459
|
+
return {
|
|
460
|
+
id: "google",
|
|
461
|
+
name: "Google",
|
|
462
|
+
createAuthorizationURL({ state, scopes, codeVerifier }) {
|
|
463
|
+
if (!codeVerifier) {
|
|
464
|
+
throw new BetterAuthError("codeVerifier is required for Google");
|
|
465
|
+
}
|
|
466
|
+
const _scopes = scopes || ["email", "profile"];
|
|
467
|
+
return googleArctic.createAuthorizationURL(state, codeVerifier, _scopes);
|
|
468
|
+
},
|
|
469
|
+
validateAuthorizationCode: async (code, codeVerifier) => {
|
|
470
|
+
if (!codeVerifier) {
|
|
471
|
+
throw new BetterAuthError("codeVerifier is required for Google");
|
|
472
|
+
}
|
|
473
|
+
return googleArctic.validateAuthorizationCode(code, codeVerifier);
|
|
474
|
+
},
|
|
475
|
+
async getUserInfo(token) {
|
|
476
|
+
if (!token.idToken) {
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
const user = parseJWT2(token.idToken())?.payload;
|
|
480
|
+
return {
|
|
481
|
+
user: {
|
|
482
|
+
id: user.sub,
|
|
483
|
+
name: user.name,
|
|
484
|
+
email: user.email,
|
|
485
|
+
image: user.picture,
|
|
486
|
+
emailVerified: user.email_verified
|
|
487
|
+
},
|
|
488
|
+
data: user
|
|
489
|
+
};
|
|
258
490
|
}
|
|
259
|
-
return res;
|
|
260
491
|
};
|
|
261
492
|
};
|
|
262
493
|
|
|
263
|
-
// src/
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
494
|
+
// src/social-providers/spotify.ts
|
|
495
|
+
import { betterFetch as betterFetch5 } from "@better-fetch/fetch";
|
|
496
|
+
import { Spotify } from "arctic";
|
|
497
|
+
var spotify = ({
|
|
498
|
+
clientId,
|
|
499
|
+
clientSecret,
|
|
500
|
+
redirectURI
|
|
501
|
+
}) => {
|
|
502
|
+
const spotifyArctic = new Spotify(
|
|
503
|
+
clientId,
|
|
504
|
+
clientSecret,
|
|
505
|
+
getRedirectURI("spotify", redirectURI)
|
|
506
|
+
);
|
|
272
507
|
return {
|
|
273
|
-
|
|
274
|
-
|
|
508
|
+
id: "spotify",
|
|
509
|
+
name: "Spotify",
|
|
510
|
+
createAuthorizationURL({ state, scopes }) {
|
|
511
|
+
const _scopes = scopes || ["user-read-email"];
|
|
512
|
+
return spotifyArctic.createAuthorizationURL(state, _scopes);
|
|
513
|
+
},
|
|
514
|
+
validateAuthorizationCode: spotifyArctic.validateAuthorizationCode,
|
|
515
|
+
async getUserInfo(token) {
|
|
516
|
+
const { data: profile, error: error2 } = await betterFetch5(
|
|
517
|
+
"https://api.spotify.com/v1/me",
|
|
518
|
+
{
|
|
519
|
+
method: "GET",
|
|
520
|
+
headers: {
|
|
521
|
+
Authorization: `Bearer ${token.accessToken}`
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
);
|
|
525
|
+
if (error2) {
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
return {
|
|
529
|
+
user: {
|
|
530
|
+
id: profile.id,
|
|
531
|
+
name: profile.display_name,
|
|
532
|
+
email: profile.email,
|
|
533
|
+
image: profile.images[0]?.url,
|
|
534
|
+
emailVerified: false
|
|
535
|
+
},
|
|
536
|
+
data: profile
|
|
537
|
+
};
|
|
538
|
+
}
|
|
275
539
|
};
|
|
276
540
|
};
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
541
|
+
|
|
542
|
+
// src/social-providers/twitch.ts
|
|
543
|
+
import { betterFetch as betterFetch6 } from "@better-fetch/fetch";
|
|
544
|
+
import { Twitch } from "arctic";
|
|
545
|
+
var twitch = ({
|
|
546
|
+
clientId,
|
|
547
|
+
clientSecret,
|
|
548
|
+
redirectURI
|
|
549
|
+
}) => {
|
|
550
|
+
const twitchArctic = new Twitch(
|
|
551
|
+
clientId,
|
|
552
|
+
clientSecret,
|
|
553
|
+
getRedirectURI("twitch", redirectURI)
|
|
280
554
|
);
|
|
281
|
-
if (!sessionFromCookie) {
|
|
282
|
-
return null;
|
|
283
|
-
}
|
|
284
|
-
const session2 = await context.adapter.findSession(sessionFromCookie, context);
|
|
285
|
-
if (!session2 || session2.expiresAt < /* @__PURE__ */ new Date()) {
|
|
286
|
-
session2 && await context.adapter.deleteSession(session2.id, context);
|
|
287
|
-
deleteSessionCooke(context);
|
|
288
|
-
return null;
|
|
289
|
-
}
|
|
290
|
-
const user = await context.adapter.findUserById(session2.userId, context);
|
|
291
|
-
if (!user) {
|
|
292
|
-
return null;
|
|
293
|
-
}
|
|
294
|
-
const updatedSession = await context.adapter.updateSession(session2, context);
|
|
295
|
-
context.request.cookies.set(context.cookies.sessionToken.name, session2.id, {
|
|
296
|
-
...context.cookies.sessionToken.options,
|
|
297
|
-
maxAge: updatedSession.expiresAt.valueOf() - Date.now()
|
|
298
|
-
});
|
|
299
|
-
sessionFromCookie;
|
|
300
555
|
return {
|
|
301
|
-
|
|
302
|
-
|
|
556
|
+
id: "twitch",
|
|
557
|
+
name: "Twitch",
|
|
558
|
+
createAuthorizationURL({ state, scopes }) {
|
|
559
|
+
const _scopes = scopes || ["activity:write", "read"];
|
|
560
|
+
return twitchArctic.createAuthorizationURL(state, _scopes);
|
|
561
|
+
},
|
|
562
|
+
validateAuthorizationCode: twitchArctic.validateAuthorizationCode,
|
|
563
|
+
async getUserInfo(token) {
|
|
564
|
+
const { data: profile, error: error2 } = await betterFetch6(
|
|
565
|
+
"https://api.twitch.tv/helix/users",
|
|
566
|
+
{
|
|
567
|
+
method: "GET",
|
|
568
|
+
headers: {
|
|
569
|
+
Authorization: `Bearer ${token.accessToken}`
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
);
|
|
573
|
+
if (error2) {
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
return {
|
|
577
|
+
user: {
|
|
578
|
+
id: profile.sub,
|
|
579
|
+
name: profile.preferred_username,
|
|
580
|
+
email: profile.email,
|
|
581
|
+
image: profile.picture,
|
|
582
|
+
emailVerified: false
|
|
583
|
+
},
|
|
584
|
+
data: profile
|
|
585
|
+
};
|
|
586
|
+
}
|
|
303
587
|
};
|
|
304
588
|
};
|
|
305
|
-
var sessionHandler = withPlugins(session);
|
|
306
589
|
|
|
307
|
-
//
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
590
|
+
// src/social-providers/twitter.ts
|
|
591
|
+
import { betterFetch as betterFetch7 } from "@better-fetch/fetch";
|
|
592
|
+
import { Twitter } from "arctic";
|
|
593
|
+
var twitter = ({
|
|
594
|
+
clientId,
|
|
595
|
+
clientSecret,
|
|
596
|
+
redirectURI
|
|
597
|
+
}) => {
|
|
598
|
+
const twitterArctic = new Twitter(
|
|
599
|
+
clientId,
|
|
600
|
+
clientSecret,
|
|
601
|
+
getRedirectURI("twitter", redirectURI)
|
|
602
|
+
);
|
|
603
|
+
return {
|
|
604
|
+
id: "twitter",
|
|
605
|
+
name: "Twitter",
|
|
606
|
+
createAuthorizationURL(data) {
|
|
607
|
+
const _scopes = data.scopes || ["account_info.read"];
|
|
608
|
+
return twitterArctic.createAuthorizationURL(
|
|
609
|
+
data.state,
|
|
610
|
+
data.codeVerifier,
|
|
611
|
+
_scopes
|
|
612
|
+
);
|
|
613
|
+
},
|
|
614
|
+
validateAuthorizationCode: async (code, codeVerifier) => {
|
|
615
|
+
if (!codeVerifier) {
|
|
616
|
+
throw new BetterAuthError("codeVerifier is required for Twitter");
|
|
617
|
+
}
|
|
618
|
+
return twitterArctic.validateAuthorizationCode(code, codeVerifier);
|
|
619
|
+
},
|
|
620
|
+
async getUserInfo(token) {
|
|
621
|
+
const { data: profile, error: error2 } = await betterFetch7(
|
|
622
|
+
"https://api.x.com/2/users/me?user.fields=profile_image_url",
|
|
623
|
+
{
|
|
624
|
+
method: "GET",
|
|
625
|
+
headers: {
|
|
626
|
+
Authorization: `Bearer ${token.accessToken}`
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
);
|
|
630
|
+
if (error2) {
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
if (!profile.data.email) {
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
return {
|
|
637
|
+
user: {
|
|
638
|
+
id: profile.data.id,
|
|
639
|
+
name: profile.data.name,
|
|
640
|
+
email: profile.data.email,
|
|
641
|
+
image: profile.data.profile_image_url,
|
|
642
|
+
emailVerified: profile.data.verified || false
|
|
643
|
+
},
|
|
644
|
+
data: profile
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
};
|
|
339
648
|
};
|
|
340
649
|
|
|
341
|
-
// src/
|
|
342
|
-
import
|
|
650
|
+
// src/types/provider.ts
|
|
651
|
+
import "arctic";
|
|
343
652
|
|
|
344
|
-
// src/
|
|
345
|
-
|
|
653
|
+
// src/social-providers/index.ts
|
|
654
|
+
var oAuthProviders = {
|
|
655
|
+
apple,
|
|
656
|
+
discord,
|
|
657
|
+
facebook,
|
|
658
|
+
github,
|
|
659
|
+
google,
|
|
660
|
+
spotify,
|
|
661
|
+
twitch,
|
|
662
|
+
twitter
|
|
663
|
+
};
|
|
664
|
+
var oAuthProviderList = Object.keys(oAuthProviders);
|
|
346
665
|
|
|
347
|
-
// src/
|
|
348
|
-
|
|
349
|
-
|
|
666
|
+
// src/utils/state.ts
|
|
667
|
+
import { generateState as generateStateOAuth } from "oslo/oauth2";
|
|
668
|
+
function generateState(callbackURL, currentURL) {
|
|
669
|
+
const code = generateStateOAuth();
|
|
670
|
+
const state = `${code}!${callbackURL}!${currentURL}`;
|
|
671
|
+
return { state, code };
|
|
672
|
+
}
|
|
673
|
+
function parseState(state) {
|
|
674
|
+
const [code, callbackURL, currentURL] = state.split("!");
|
|
675
|
+
return { code, callbackURL, currentURL };
|
|
350
676
|
}
|
|
351
677
|
|
|
352
|
-
// src/
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
throw new TypeError('"issuerIdentifier" must be an instance of URL');
|
|
357
|
-
}
|
|
358
|
-
if (issuerIdentifier.protocol !== "https:" && issuerIdentifier.protocol !== "http:") {
|
|
359
|
-
throw new TypeError('"issuer.protocol" must be "https:" or "http:"');
|
|
360
|
-
}
|
|
361
|
-
const url = new URL(issuerIdentifier.href);
|
|
362
|
-
switch (provider.type) {
|
|
363
|
-
case void 0:
|
|
364
|
-
case "oidc":
|
|
365
|
-
url.pathname = `${url.pathname}/.well-known/openid-configuration`.replace(
|
|
366
|
-
"//",
|
|
367
|
-
"/"
|
|
368
|
-
);
|
|
369
|
-
break;
|
|
370
|
-
case "oauth":
|
|
371
|
-
if (url.pathname === "/") {
|
|
372
|
-
url.pathname = ".well-known/oauth-authorization-server";
|
|
373
|
-
} else {
|
|
374
|
-
url.pathname = `.well-known/oauth-authorization-server/${url.pathname}`.replace(
|
|
375
|
-
"//",
|
|
376
|
-
"/"
|
|
377
|
-
);
|
|
378
|
-
}
|
|
379
|
-
break;
|
|
380
|
-
default:
|
|
381
|
-
throw new TypeError(`"provider.type" must be "oidc" or "oauth"`);
|
|
382
|
-
}
|
|
383
|
-
const headers = new Headers(context.request.headers);
|
|
384
|
-
headers.set("accept", "application/json");
|
|
385
|
-
return fetch(url.href, {
|
|
386
|
-
headers: Object.fromEntries(headers.entries()),
|
|
678
|
+
// src/api/routes/session.ts
|
|
679
|
+
var getSession = createAuthEndpoint(
|
|
680
|
+
"/session",
|
|
681
|
+
{
|
|
387
682
|
method: "GET",
|
|
388
|
-
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
|
|
683
|
+
requireHeaders: true
|
|
684
|
+
},
|
|
685
|
+
async (ctx) => {
|
|
686
|
+
const sessionCookieToken = await ctx.getSignedCookie(
|
|
687
|
+
ctx.context.authCookies.sessionToken.name,
|
|
688
|
+
ctx.context.secret
|
|
689
|
+
);
|
|
690
|
+
if (!sessionCookieToken) {
|
|
691
|
+
return ctx.json(null, {
|
|
692
|
+
status: 401
|
|
693
|
+
});
|
|
392
694
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
autoCreateSession: true,
|
|
403
|
-
onlySignUp: false
|
|
404
|
-
}) {
|
|
405
|
-
if (!provider.params.clientId) {
|
|
406
|
-
throw new ProviderError("clientId is required");
|
|
407
|
-
}
|
|
408
|
-
const scopes = Array.from(new Set(provider?.scopes ?? []));
|
|
409
|
-
const { currentURL, callbackURL, data } = context.request.body;
|
|
410
|
-
const state = generateState(
|
|
411
|
-
currentURL,
|
|
412
|
-
callbackURL,
|
|
413
|
-
data,
|
|
414
|
-
autoCreateSession,
|
|
415
|
-
onlySignUp
|
|
416
|
-
);
|
|
417
|
-
let url = provider.params.authorizationEndpoint;
|
|
418
|
-
if (!url) {
|
|
419
|
-
const discovery = await discoveryRequest(context, provider);
|
|
420
|
-
if (!discovery.authorization_endpoint)
|
|
421
|
-
throw new ProviderError("Missing authorization endpoint");
|
|
422
|
-
url = discovery.authorization_endpoint;
|
|
423
|
-
}
|
|
424
|
-
const authorizationUrl = new URL(url);
|
|
425
|
-
authorizationUrl.searchParams.set("response_type", "code");
|
|
426
|
-
authorizationUrl.searchParams.set("client_id", provider.params.clientId);
|
|
427
|
-
authorizationUrl.searchParams.set("state", state);
|
|
428
|
-
authorizationUrl.searchParams.set("scope", scopes.join(" "));
|
|
429
|
-
authorizationUrl.searchParams.set(
|
|
430
|
-
"redirect_uri",
|
|
431
|
-
provider.params.redirectURL || `${context.request.url.toString()}/callback/${context.request.body.provider}`
|
|
432
|
-
);
|
|
433
|
-
if (provider.type === "oidc") {
|
|
434
|
-
if (provider.nonce) {
|
|
435
|
-
const nonce = generateRandomString(24);
|
|
436
|
-
authorizationUrl.searchParams.set("nonce", nonce);
|
|
437
|
-
context.request.cookies.set(
|
|
438
|
-
context.cookies.nonce.name,
|
|
439
|
-
nonce,
|
|
440
|
-
context.cookies.nonce.options
|
|
695
|
+
const session = await ctx.context.internalAdapter.findSession(sessionCookieToken);
|
|
696
|
+
if (!session || session.session.expiresAt < /* @__PURE__ */ new Date()) {
|
|
697
|
+
ctx.setSignedCookie(
|
|
698
|
+
ctx.context.authCookies.sessionToken.name,
|
|
699
|
+
"",
|
|
700
|
+
ctx.context.secret,
|
|
701
|
+
{
|
|
702
|
+
maxAge: 0
|
|
703
|
+
}
|
|
441
704
|
);
|
|
705
|
+
return ctx.json(null, {
|
|
706
|
+
status: 401
|
|
707
|
+
});
|
|
442
708
|
}
|
|
709
|
+
const updatedSession = await ctx.context.internalAdapter.updateSession(
|
|
710
|
+
session.session
|
|
711
|
+
);
|
|
712
|
+
await ctx.setSignedCookie(
|
|
713
|
+
ctx.context.authCookies.sessionToken.name,
|
|
714
|
+
updatedSession.id,
|
|
715
|
+
ctx.context.secret,
|
|
716
|
+
{
|
|
717
|
+
...ctx.context.authCookies.sessionToken.options,
|
|
718
|
+
maxAge: updatedSession.expiresAt.valueOf() - Date.now()
|
|
719
|
+
}
|
|
720
|
+
);
|
|
721
|
+
return ctx.json({
|
|
722
|
+
session: updatedSession,
|
|
723
|
+
user: session.user
|
|
724
|
+
});
|
|
443
725
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
726
|
+
);
|
|
727
|
+
var getSessionFromCtx = async (ctx) => {
|
|
728
|
+
const session = await getSession({
|
|
729
|
+
...ctx,
|
|
730
|
+
//@ts-expect-error: By default since this request context comes from a router it'll have a `router` flag which force it to be a request object
|
|
731
|
+
_flag: void 0
|
|
732
|
+
});
|
|
733
|
+
return session;
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
// src/api/routes/sign-in.ts
|
|
737
|
+
var signInOAuth = createAuthEndpoint(
|
|
738
|
+
"/sign-in/social",
|
|
739
|
+
{
|
|
740
|
+
method: "POST",
|
|
741
|
+
requireHeaders: true,
|
|
742
|
+
query: z3.object({
|
|
743
|
+
/**
|
|
744
|
+
* Redirect to the current URL after the
|
|
745
|
+
* user has signed in.
|
|
746
|
+
*/
|
|
747
|
+
currentURL: z3.string().optional()
|
|
748
|
+
}).optional(),
|
|
749
|
+
body: z3.object({
|
|
750
|
+
/**
|
|
751
|
+
* Callback URL to redirect to after the user has signed in.
|
|
752
|
+
*/
|
|
753
|
+
callbackURL: z3.string().optional(),
|
|
754
|
+
/**
|
|
755
|
+
* OAuth2 provider to use`
|
|
756
|
+
*/
|
|
757
|
+
provider: z3.enum(oAuthProviderList)
|
|
758
|
+
})
|
|
759
|
+
},
|
|
760
|
+
async (c) => {
|
|
761
|
+
const provider = c.context.options.socialProvider?.find(
|
|
762
|
+
(p) => p.id === c.body.provider
|
|
763
|
+
);
|
|
764
|
+
if (!provider) {
|
|
765
|
+
c.context.logger.error(
|
|
766
|
+
"Provider not found. Make sure to add the provider to your auth config",
|
|
767
|
+
{
|
|
768
|
+
provider: c.body.provider
|
|
769
|
+
}
|
|
463
770
|
);
|
|
464
|
-
|
|
465
|
-
authorizationUrl.searchParams.set("code_challenge_method", "S256");
|
|
466
|
-
} else {
|
|
467
|
-
authorizationUrl.searchParams.set("code_challenge", codeVerifier);
|
|
468
|
-
authorizationUrl.searchParams.set("code_challenge_method", "plain");
|
|
771
|
+
throw new APIError2("NOT_FOUND");
|
|
469
772
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
);
|
|
476
|
-
if (codeVerifier) {
|
|
477
|
-
context.request.cookies.set(
|
|
478
|
-
context.cookies.pkCodeVerifier.name,
|
|
479
|
-
codeVerifier,
|
|
480
|
-
context.cookies.pkCodeVerifier.options
|
|
773
|
+
const cookie = c.context.authCookies;
|
|
774
|
+
const currentURL = c.query?.currentURL ? new URL(c.query?.currentURL) : null;
|
|
775
|
+
const state = generateState(
|
|
776
|
+
c.body.callbackURL || currentURL?.origin || c.context.baseURL,
|
|
777
|
+
c.query?.currentURL
|
|
481
778
|
);
|
|
779
|
+
try {
|
|
780
|
+
await c.setSignedCookie(
|
|
781
|
+
cookie.state.name,
|
|
782
|
+
state.code,
|
|
783
|
+
c.context.secret,
|
|
784
|
+
cookie.state.options
|
|
785
|
+
);
|
|
786
|
+
const codeVerifier = generateCodeVerifier();
|
|
787
|
+
await c.setSignedCookie(
|
|
788
|
+
cookie.pkCodeVerifier.name,
|
|
789
|
+
codeVerifier,
|
|
790
|
+
c.context.secret,
|
|
791
|
+
cookie.pkCodeVerifier.options
|
|
792
|
+
);
|
|
793
|
+
const url = provider.createAuthorizationURL({
|
|
794
|
+
state: state.state,
|
|
795
|
+
codeVerifier
|
|
796
|
+
});
|
|
797
|
+
return {
|
|
798
|
+
url: url.toString(),
|
|
799
|
+
state: state.state,
|
|
800
|
+
codeVerifier,
|
|
801
|
+
redirect: true
|
|
802
|
+
};
|
|
803
|
+
} catch (e) {
|
|
804
|
+
throw new APIError2("INTERNAL_SERVER_ERROR");
|
|
805
|
+
}
|
|
482
806
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
}
|
|
506
|
-
const signUp2 = signUpString ? JSON.parse(signUpString) : void 0;
|
|
507
|
-
return {
|
|
508
|
-
hash,
|
|
509
|
-
currentURL,
|
|
510
|
-
callbackURL,
|
|
511
|
-
signUp: {
|
|
512
|
-
data: signUp2?.data,
|
|
513
|
-
autoCreateSession: signUp2?.autoCreateSession,
|
|
514
|
-
onlySignUp: signUp2?.onlySignUp
|
|
807
|
+
);
|
|
808
|
+
var signInEmail = createAuthEndpoint(
|
|
809
|
+
"/sign-in/email",
|
|
810
|
+
{
|
|
811
|
+
method: "POST",
|
|
812
|
+
body: z3.object({
|
|
813
|
+
email: z3.string().email(),
|
|
814
|
+
password: z3.string(),
|
|
815
|
+
callbackURL: z3.string().optional(),
|
|
816
|
+
/**
|
|
817
|
+
* If this is true the session will only be valid for the current browser session
|
|
818
|
+
* @default false
|
|
819
|
+
*/
|
|
820
|
+
dontRememberMe: z3.boolean().default(false).optional()
|
|
821
|
+
})
|
|
822
|
+
},
|
|
823
|
+
async (ctx) => {
|
|
824
|
+
if (!ctx.context.options?.emailAndPassword?.enabled) {
|
|
825
|
+
ctx.context.logger.error("Email and password is not enabled");
|
|
826
|
+
throw new APIError2("BAD_REQUEST", {
|
|
827
|
+
message: "Email and password is not enabled"
|
|
828
|
+
});
|
|
515
829
|
}
|
|
516
|
-
|
|
517
|
-
|
|
830
|
+
const currentSession = await getSessionFromCtx(ctx);
|
|
831
|
+
if (currentSession) {
|
|
832
|
+
return ctx.json({
|
|
833
|
+
user: currentSession.user,
|
|
834
|
+
session: currentSession.session,
|
|
835
|
+
redirect: !!ctx.body.callbackURL,
|
|
836
|
+
url: ctx.body.callbackURL
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
const { email, password } = ctx.body;
|
|
840
|
+
const argon2id = new Argon2id();
|
|
841
|
+
const user = await ctx.context.internalAdapter.findUserByEmail(email);
|
|
842
|
+
if (!user) {
|
|
843
|
+
await argon2id.hash(password);
|
|
844
|
+
ctx.context.logger.error("User not found", { email });
|
|
845
|
+
throw new APIError2("UNAUTHORIZED", {
|
|
846
|
+
message: "Invalid email or password"
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
const credentialAccount = user.accounts.find(
|
|
850
|
+
(a) => a.providerId === "credential"
|
|
851
|
+
);
|
|
852
|
+
if (!credentialAccount) {
|
|
853
|
+
ctx.context.logger.error("Credential account not found", { email });
|
|
854
|
+
throw new APIError2("UNAUTHORIZED", {
|
|
855
|
+
message: "Invalid email or password"
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
const currentPassword = credentialAccount?.password;
|
|
859
|
+
if (!currentPassword) {
|
|
860
|
+
ctx.context.logger.error("Password not found", { email });
|
|
861
|
+
throw new APIError2("UNAUTHORIZED", {
|
|
862
|
+
message: "Unexpected error"
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
const validPassword = await argon2id.verify(currentPassword, password);
|
|
866
|
+
if (!validPassword) {
|
|
867
|
+
ctx.context.logger.error("Invalid password");
|
|
868
|
+
throw new APIError2("UNAUTHORIZED", {
|
|
869
|
+
message: "Invalid email or password"
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
const session = await ctx.context.internalAdapter.createSession(
|
|
873
|
+
user.user.id,
|
|
874
|
+
ctx.request
|
|
875
|
+
);
|
|
876
|
+
await ctx.setSignedCookie(
|
|
877
|
+
ctx.context.authCookies.sessionToken.name,
|
|
878
|
+
session.id,
|
|
879
|
+
ctx.context.secret,
|
|
880
|
+
ctx.body.dontRememberMe ? {
|
|
881
|
+
...ctx.context.authCookies.sessionToken.options,
|
|
882
|
+
maxAge: void 0
|
|
883
|
+
} : ctx.context.authCookies.sessionToken.options
|
|
884
|
+
);
|
|
885
|
+
return ctx.json({
|
|
886
|
+
user: user.user,
|
|
887
|
+
session,
|
|
888
|
+
redirect: !!ctx.body.callbackURL,
|
|
889
|
+
url: ctx.body.callbackURL
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
);
|
|
893
|
+
|
|
894
|
+
// src/api/routes/callback.ts
|
|
895
|
+
import { APIError as APIError3 } from "better-call";
|
|
896
|
+
import { z as z4 } from "zod";
|
|
518
897
|
|
|
519
|
-
// src/
|
|
520
|
-
var
|
|
521
|
-
|
|
522
|
-
const provider = providers.find((provider2) => provider2.id === providerId);
|
|
523
|
-
return provider;
|
|
898
|
+
// src/client/client-utils.ts
|
|
899
|
+
var HIDE_ON_CLIENT_METADATA = {
|
|
900
|
+
onClient: "hide"
|
|
524
901
|
};
|
|
525
902
|
|
|
526
|
-
// src/
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
var
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
903
|
+
// src/utils/id.ts
|
|
904
|
+
import { alphabet, generateRandomString } from "oslo/crypto";
|
|
905
|
+
var generateId = () => {
|
|
906
|
+
return generateRandomString(36, alphabet("a-z", "0-9"));
|
|
907
|
+
};
|
|
908
|
+
|
|
909
|
+
// src/api/routes/callback.ts
|
|
910
|
+
var callbackOAuth = createAuthEndpoint(
|
|
911
|
+
"/callback/:id",
|
|
912
|
+
{
|
|
913
|
+
method: "GET",
|
|
914
|
+
query: z4.object({
|
|
915
|
+
state: z4.string(),
|
|
916
|
+
code: z4.string(),
|
|
917
|
+
code_verifier: z4.string().optional()
|
|
918
|
+
}),
|
|
919
|
+
metadata: HIDE_ON_CLIENT_METADATA
|
|
920
|
+
},
|
|
921
|
+
async (c) => {
|
|
922
|
+
const provider = c.context.options.socialProvider?.find(
|
|
923
|
+
(p) => p.id === c.params.id
|
|
924
|
+
);
|
|
925
|
+
if (!provider) {
|
|
926
|
+
c.context.logger.error(
|
|
927
|
+
"Oauth provider with id",
|
|
928
|
+
c.params.id,
|
|
929
|
+
"not found"
|
|
930
|
+
);
|
|
931
|
+
throw new APIError3("NOT_FOUND");
|
|
932
|
+
}
|
|
933
|
+
const tokens = await provider.validateAuthorizationCode(
|
|
934
|
+
c.query.code,
|
|
935
|
+
c.query.code_verifier || ""
|
|
936
|
+
);
|
|
937
|
+
if (!tokens) {
|
|
938
|
+
c.context.logger.error("Code verification failed");
|
|
939
|
+
throw new APIError3("UNAUTHORIZED");
|
|
940
|
+
}
|
|
941
|
+
const user = await provider.getUserInfo(tokens).then((res) => res?.user);
|
|
942
|
+
const id = generateId();
|
|
943
|
+
const data = userSchema.safeParse({
|
|
944
|
+
...user,
|
|
945
|
+
id
|
|
946
|
+
});
|
|
947
|
+
const { callbackURL, currentURL } = parseState(c.query.state);
|
|
948
|
+
if (!user || data.success === false) {
|
|
949
|
+
if (currentURL) {
|
|
950
|
+
throw c.redirect(`${currentURL}?error=oauth_validation_failed`);
|
|
951
|
+
} else {
|
|
952
|
+
throw new APIError3("BAD_REQUEST");
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
if (!callbackURL) {
|
|
956
|
+
c.context.logger.error("Callback URL not found");
|
|
957
|
+
throw new APIError3("FORBIDDEN");
|
|
958
|
+
}
|
|
959
|
+
const dbUser = await c.context.internalAdapter.findUserByEmail(user.email);
|
|
960
|
+
const userId = dbUser?.user.id;
|
|
961
|
+
if (dbUser) {
|
|
962
|
+
const hasBeenLinked = dbUser.accounts.find(
|
|
963
|
+
(a) => a.providerId === provider.id
|
|
964
|
+
);
|
|
965
|
+
if (!hasBeenLinked && !user.emailVerified) {
|
|
966
|
+
c.context.logger.error("User already exists");
|
|
967
|
+
const url = new URL(currentURL || callbackURL);
|
|
968
|
+
url.searchParams.set("error", "user_already_exists");
|
|
969
|
+
throw c.redirect(url.toString());
|
|
970
|
+
}
|
|
971
|
+
if (!hasBeenLinked && user.emailVerified) {
|
|
972
|
+
await c.context.internalAdapter.linkAccount({
|
|
973
|
+
providerId: provider.id,
|
|
974
|
+
accountId: user.id,
|
|
975
|
+
id: `${provider.id}:${user.id}`,
|
|
976
|
+
userId: dbUser.user.id,
|
|
977
|
+
...tokens
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
} else {
|
|
981
|
+
try {
|
|
982
|
+
await c.context.internalAdapter.createOAuthUser(data.data, {
|
|
983
|
+
...tokens,
|
|
984
|
+
id: `${provider.id}:${user.id}`,
|
|
985
|
+
providerId: provider.id,
|
|
986
|
+
accountId: user.id,
|
|
987
|
+
userId: id
|
|
988
|
+
});
|
|
989
|
+
} catch (e) {
|
|
990
|
+
const url = new URL(currentURL || callbackURL);
|
|
991
|
+
url.searchParams.set("error", "unable_to_create_user");
|
|
992
|
+
c.setHeader("Location", url.toString());
|
|
993
|
+
throw c.redirect(url.toString());
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
if (!userId && !id)
|
|
997
|
+
throw new APIError3("INTERNAL_SERVER_ERROR", {
|
|
998
|
+
message: "Unable to create user"
|
|
999
|
+
});
|
|
1000
|
+
const session = await c.context.internalAdapter.createSession(
|
|
1001
|
+
userId || id,
|
|
1002
|
+
c.request
|
|
1003
|
+
);
|
|
1004
|
+
try {
|
|
1005
|
+
await c.setSignedCookie(
|
|
1006
|
+
c.context.authCookies.sessionToken.name,
|
|
1007
|
+
session.id,
|
|
1008
|
+
c.context.secret,
|
|
1009
|
+
c.context.authCookies.sessionToken.options
|
|
1010
|
+
);
|
|
1011
|
+
} catch (e) {
|
|
1012
|
+
c.context.logger.error("Unable to set session cookie", e);
|
|
1013
|
+
const url = new URL(currentURL || callbackURL);
|
|
1014
|
+
url.searchParams.set("error", "unable_to_create_session");
|
|
1015
|
+
throw c.redirect(url.toString());
|
|
1016
|
+
}
|
|
1017
|
+
throw c.redirect(callbackURL);
|
|
538
1018
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
1019
|
+
);
|
|
1020
|
+
|
|
1021
|
+
// src/api/routes/sign-out.ts
|
|
1022
|
+
import { z as z5 } from "zod";
|
|
1023
|
+
var signOut = createAuthEndpoint(
|
|
1024
|
+
"/sign-out",
|
|
1025
|
+
{
|
|
1026
|
+
method: "POST",
|
|
1027
|
+
body: z5.object({
|
|
1028
|
+
callbackURL: z5.string().optional()
|
|
1029
|
+
}).optional()
|
|
1030
|
+
},
|
|
1031
|
+
async (ctx) => {
|
|
1032
|
+
const sessionCookieToken = await ctx.getSignedCookie(
|
|
1033
|
+
ctx.context.authCookies.sessionToken.name,
|
|
1034
|
+
ctx.context.secret
|
|
1035
|
+
);
|
|
1036
|
+
if (!sessionCookieToken) {
|
|
1037
|
+
return ctx.json(null);
|
|
1038
|
+
}
|
|
1039
|
+
await ctx.context.internalAdapter.deleteSession(sessionCookieToken);
|
|
1040
|
+
ctx.setCookie(ctx.context.authCookies.sessionToken.name, "", {
|
|
1041
|
+
maxAge: 0
|
|
1042
|
+
});
|
|
1043
|
+
return ctx.json(null, {
|
|
543
1044
|
body: {
|
|
544
|
-
|
|
545
|
-
|
|
1045
|
+
redirect: !!ctx.body?.callbackURL,
|
|
1046
|
+
url: ctx.body?.callbackURL
|
|
546
1047
|
}
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
);
|
|
1051
|
+
|
|
1052
|
+
// src/api/routes/forget-password.ts
|
|
1053
|
+
import { TimeSpan } from "oslo";
|
|
1054
|
+
import { createJWT } from "oslo/jwt";
|
|
1055
|
+
import { validateJWT } from "oslo/jwt";
|
|
1056
|
+
import { Argon2id as Argon2id2 } from "oslo/password";
|
|
1057
|
+
import { z as z6 } from "zod";
|
|
1058
|
+
var forgetPassword = createAuthEndpoint(
|
|
1059
|
+
"/forget-password",
|
|
1060
|
+
{
|
|
1061
|
+
method: "POST",
|
|
1062
|
+
body: z6.object({
|
|
1063
|
+
/**
|
|
1064
|
+
* The email address of the user to send a password reset email to.
|
|
1065
|
+
*/
|
|
1066
|
+
email: z6.string().email()
|
|
1067
|
+
})
|
|
1068
|
+
},
|
|
1069
|
+
async (ctx) => {
|
|
1070
|
+
if (!ctx.context.options.emailAndPassword?.sendResetPasswordToken) {
|
|
1071
|
+
ctx.context.logger.error(
|
|
1072
|
+
"Reset password isn't enabled.Please pass an emailAndPassword.sendResetPasswordToken function to your auth config!"
|
|
1073
|
+
);
|
|
1074
|
+
return ctx.json(null, {
|
|
1075
|
+
status: 400,
|
|
1076
|
+
statusText: "RESET_PASSWORD_EMAIL_NOT_SENT",
|
|
1077
|
+
body: {
|
|
1078
|
+
message: "Reset password isn't enabled"
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
const { email } = ctx.body;
|
|
1083
|
+
const user = await ctx.context.internalAdapter.findUserByEmail(email);
|
|
1084
|
+
if (!user) {
|
|
1085
|
+
return ctx.json(
|
|
1086
|
+
{
|
|
1087
|
+
error: "User not found"
|
|
1088
|
+
},
|
|
1089
|
+
{
|
|
1090
|
+
status: 400,
|
|
1091
|
+
statusText: "USER_NOT_FOUND",
|
|
1092
|
+
body: {
|
|
1093
|
+
message: "User not found"
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
1098
|
+
const token = await createJWT(
|
|
1099
|
+
"HS256",
|
|
1100
|
+
Buffer.from(ctx.context.secret),
|
|
1101
|
+
{
|
|
1102
|
+
email: user.user.email
|
|
1103
|
+
},
|
|
1104
|
+
{
|
|
1105
|
+
expiresIn: new TimeSpan(1, "h"),
|
|
1106
|
+
issuer: "better-auth",
|
|
1107
|
+
subject: "forget-password",
|
|
1108
|
+
audiences: [user.user.email],
|
|
1109
|
+
includeIssuedTimestamp: true
|
|
1110
|
+
}
|
|
1111
|
+
);
|
|
1112
|
+
await ctx.context.options.emailAndPassword.sendResetPasswordToken(
|
|
1113
|
+
token,
|
|
1114
|
+
user.user
|
|
1115
|
+
);
|
|
1116
|
+
return ctx.json({
|
|
1117
|
+
status: true
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
);
|
|
1121
|
+
var resetPassword = createAuthEndpoint(
|
|
1122
|
+
"/reset-password",
|
|
1123
|
+
{
|
|
1124
|
+
method: "POST",
|
|
1125
|
+
body: z6.object({
|
|
1126
|
+
token: z6.string(),
|
|
1127
|
+
newPassword: z6.string(),
|
|
1128
|
+
callbackURL: z6.string().optional()
|
|
1129
|
+
})
|
|
1130
|
+
},
|
|
1131
|
+
async (ctx) => {
|
|
1132
|
+
const { token, newPassword } = ctx.body;
|
|
1133
|
+
try {
|
|
1134
|
+
const jwt = await validateJWT(
|
|
1135
|
+
"HS256",
|
|
1136
|
+
Buffer.from(ctx.context.secret),
|
|
1137
|
+
token
|
|
1138
|
+
);
|
|
1139
|
+
const email = z6.string().email().parse(jwt.payload.email);
|
|
1140
|
+
const user = await ctx.context.internalAdapter.findUserByEmail(email);
|
|
1141
|
+
if (!user) {
|
|
1142
|
+
return ctx.json(null, {
|
|
1143
|
+
status: 400,
|
|
1144
|
+
statusText: "USER_NOT_FOUND",
|
|
1145
|
+
body: {
|
|
1146
|
+
message: "User not found"
|
|
1147
|
+
}
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
if (newPassword.length < (ctx.context.options.emailAndPassword?.minPasswordLength || 8) || newPassword.length > (ctx.context.options.emailAndPassword?.maxPasswordLength || 32)) {
|
|
1151
|
+
return ctx.json(null, {
|
|
1152
|
+
status: 400,
|
|
1153
|
+
statusText: "INVALID_PASSWORD_LENGTH",
|
|
1154
|
+
body: {
|
|
1155
|
+
message: "Password length must be between 8 and 32"
|
|
1156
|
+
}
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
const argon2id = new Argon2id2();
|
|
1160
|
+
const hashedPassword = await argon2id.hash(newPassword);
|
|
1161
|
+
const updatedUser = await ctx.context.internalAdapter.updatePassword(
|
|
1162
|
+
user.user.id,
|
|
1163
|
+
hashedPassword
|
|
1164
|
+
);
|
|
1165
|
+
if (!updatedUser) {
|
|
1166
|
+
return ctx.json(null, {
|
|
1167
|
+
status: 500,
|
|
1168
|
+
statusText: "INTERNAL_SERVER_ERROR",
|
|
1169
|
+
body: {
|
|
1170
|
+
message: "Internal server error"
|
|
1171
|
+
}
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
return ctx.json({
|
|
1175
|
+
status: true,
|
|
1176
|
+
url: ctx.body.callbackURL,
|
|
1177
|
+
redirect: !!ctx.body.callbackURL
|
|
1178
|
+
});
|
|
1179
|
+
} catch (e) {
|
|
1180
|
+
console.log(e);
|
|
1181
|
+
return ctx.json(null, {
|
|
1182
|
+
status: 400,
|
|
1183
|
+
statusText: "INVALID_TOKEN",
|
|
1184
|
+
body: {
|
|
1185
|
+
message: "Invalid token"
|
|
1186
|
+
}
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
);
|
|
1191
|
+
|
|
1192
|
+
// src/api/routes/verify-email.ts
|
|
1193
|
+
import { TimeSpan as TimeSpan2 } from "oslo";
|
|
1194
|
+
import { createJWT as createJWT2, validateJWT as validateJWT2 } from "oslo/jwt";
|
|
1195
|
+
import { z as z7 } from "zod";
|
|
1196
|
+
var sendVerificationEmail = createAuthEndpoint(
|
|
1197
|
+
"/send-verification-email",
|
|
1198
|
+
{
|
|
1199
|
+
method: "POST",
|
|
1200
|
+
body: z7.object({
|
|
1201
|
+
email: z7.string().email(),
|
|
1202
|
+
callbackURL: z7.string().optional()
|
|
1203
|
+
})
|
|
1204
|
+
},
|
|
1205
|
+
async (ctx) => {
|
|
1206
|
+
if (!ctx.context.options.emailAndPassword?.sendVerificationEmail) {
|
|
1207
|
+
return ctx.json(null, {
|
|
1208
|
+
status: 400,
|
|
1209
|
+
statusText: "VERIFICATION_EMAIL_NOT_SENT",
|
|
1210
|
+
body: {
|
|
1211
|
+
message: "Verification email isn't enabled"
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
const { email } = ctx.body;
|
|
1216
|
+
const token = await createJWT2(
|
|
1217
|
+
"HS256",
|
|
1218
|
+
Buffer.from(ctx.context.secret),
|
|
1219
|
+
{
|
|
1220
|
+
email: email.toLowerCase()
|
|
1221
|
+
},
|
|
1222
|
+
{
|
|
1223
|
+
expiresIn: new TimeSpan2(1, "h"),
|
|
1224
|
+
issuer: "better-auth",
|
|
1225
|
+
subject: "verify-email",
|
|
1226
|
+
audiences: [email],
|
|
1227
|
+
includeIssuedTimestamp: true
|
|
1228
|
+
}
|
|
1229
|
+
);
|
|
1230
|
+
const url = `${ctx.context.baseURL}/verify-email?token=${token}?callbackURL=${ctx.body.callbackURL}`;
|
|
1231
|
+
await ctx.context.options.emailAndPassword.sendVerificationEmail(
|
|
1232
|
+
email,
|
|
1233
|
+
url
|
|
1234
|
+
);
|
|
1235
|
+
return ctx.json({
|
|
1236
|
+
status: true
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
);
|
|
1240
|
+
var verifyEmail = createAuthEndpoint(
|
|
1241
|
+
"/verify-email",
|
|
1242
|
+
{
|
|
1243
|
+
method: "GET",
|
|
1244
|
+
query: z7.object({
|
|
1245
|
+
token: z7.string(),
|
|
1246
|
+
callbackURL: z7.string()
|
|
1247
|
+
})
|
|
1248
|
+
},
|
|
1249
|
+
async (ctx) => {
|
|
1250
|
+
const { token } = ctx.query;
|
|
1251
|
+
try {
|
|
1252
|
+
const jwt = await validateJWT2(
|
|
1253
|
+
"HS256",
|
|
1254
|
+
Buffer.from(ctx.context.secret),
|
|
1255
|
+
token
|
|
1256
|
+
);
|
|
1257
|
+
const schema = z7.object({
|
|
1258
|
+
email: z7.string().email()
|
|
1259
|
+
});
|
|
1260
|
+
const parsed = schema.parse(jwt.payload);
|
|
1261
|
+
const user = await ctx.context.internalAdapter.findUserByEmail(
|
|
1262
|
+
parsed.email
|
|
1263
|
+
);
|
|
1264
|
+
if (!user) {
|
|
1265
|
+
return ctx.json(null, {
|
|
1266
|
+
status: 400,
|
|
1267
|
+
statusText: "USER_NOT_FOUND",
|
|
1268
|
+
body: {
|
|
1269
|
+
message: "User not found"
|
|
1270
|
+
}
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
const account = user.accounts.find((a) => a.providerId === "credential");
|
|
1274
|
+
if (!account) {
|
|
1275
|
+
return ctx.json(null, {
|
|
1276
|
+
status: 400,
|
|
1277
|
+
statusText: "ACCOUNT_NOT_FOUND",
|
|
1278
|
+
body: {
|
|
1279
|
+
message: "Account not found"
|
|
1280
|
+
}
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
await ctx.context.internalAdapter.updateUserByEmail(parsed.email, {
|
|
1284
|
+
emailVerified: true
|
|
1285
|
+
});
|
|
1286
|
+
if (ctx.query.callbackURL) {
|
|
1287
|
+
throw ctx.redirect(ctx.query.callbackURL);
|
|
1288
|
+
}
|
|
1289
|
+
return ctx.json({
|
|
1290
|
+
status: true
|
|
1291
|
+
});
|
|
1292
|
+
} catch (e) {
|
|
1293
|
+
return ctx.json(null, {
|
|
1294
|
+
status: 400,
|
|
1295
|
+
statusText: "INVALID_TOKEN",
|
|
1296
|
+
body: {
|
|
1297
|
+
message: "Invalid token"
|
|
1298
|
+
}
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
);
|
|
1303
|
+
|
|
1304
|
+
// src/api/routes/csrf.ts
|
|
1305
|
+
import { alphabet as alphabet2, generateRandomString as generateRandomString2 } from "oslo/crypto";
|
|
1306
|
+
var getCSRFToken = createAuthEndpoint(
|
|
1307
|
+
"/csrf",
|
|
1308
|
+
{
|
|
1309
|
+
method: "GET",
|
|
1310
|
+
metadata: HIDE_ON_CLIENT_METADATA
|
|
1311
|
+
},
|
|
1312
|
+
async (ctx) => {
|
|
1313
|
+
const csrfToken = await ctx.getSignedCookie(
|
|
1314
|
+
ctx.context.authCookies.csrfToken.name,
|
|
1315
|
+
ctx.context.secret
|
|
1316
|
+
);
|
|
1317
|
+
if (csrfToken) {
|
|
1318
|
+
return {
|
|
1319
|
+
csrfToken
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
const token = generateRandomString2(32, alphabet2("a-z", "0-9", "A-Z"));
|
|
1323
|
+
const hash = await hs256(ctx.context.secret, token);
|
|
1324
|
+
const cookie = `${token}!${hash}`;
|
|
1325
|
+
await ctx.setSignedCookie(
|
|
1326
|
+
ctx.context.authCookies.csrfToken.name,
|
|
1327
|
+
cookie,
|
|
1328
|
+
ctx.context.secret,
|
|
1329
|
+
ctx.context.authCookies.csrfToken.options
|
|
1330
|
+
);
|
|
1331
|
+
return {
|
|
1332
|
+
csrfToken: token
|
|
547
1333
|
};
|
|
548
1334
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
1335
|
+
);
|
|
1336
|
+
|
|
1337
|
+
// src/api/routes/ok.ts
|
|
1338
|
+
var ok = createAuthEndpoint(
|
|
1339
|
+
"/ok",
|
|
1340
|
+
{
|
|
1341
|
+
method: "GET"
|
|
1342
|
+
},
|
|
1343
|
+
async (ctx) => {
|
|
1344
|
+
return ctx.json({
|
|
1345
|
+
ok: true
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
);
|
|
1349
|
+
var welcome = createAuthEndpoint(
|
|
1350
|
+
"/welcome/ok",
|
|
1351
|
+
{
|
|
1352
|
+
method: "GET"
|
|
1353
|
+
},
|
|
1354
|
+
async () => {
|
|
1355
|
+
return new Response("Welcome to Better Auth");
|
|
1356
|
+
}
|
|
1357
|
+
);
|
|
1358
|
+
|
|
1359
|
+
// src/api/routes/sign-up.ts
|
|
1360
|
+
import { alphabet as alphabet3, generateRandomString as generateRandomString3 } from "oslo/crypto";
|
|
1361
|
+
import { Argon2id as Argon2id3 } from "oslo/password";
|
|
1362
|
+
import { z as z8 } from "zod";
|
|
1363
|
+
var signUpEmail = createAuthEndpoint(
|
|
1364
|
+
"/sign-up/email",
|
|
1365
|
+
{
|
|
1366
|
+
method: "POST",
|
|
1367
|
+
body: z8.object({
|
|
1368
|
+
name: z8.string(),
|
|
1369
|
+
email: z8.string().email(),
|
|
1370
|
+
password: z8.string(),
|
|
1371
|
+
image: z8.string().optional(),
|
|
1372
|
+
callbackURL: z8.string().optional()
|
|
1373
|
+
})
|
|
1374
|
+
},
|
|
1375
|
+
async (ctx) => {
|
|
1376
|
+
if (!ctx.context.options.emailAndPassword?.enabled) {
|
|
1377
|
+
return ctx.json(null, {
|
|
1378
|
+
status: 400,
|
|
1379
|
+
body: {
|
|
1380
|
+
message: "Email and password is not enabled"
|
|
1381
|
+
}
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
const { name, email, password, image } = ctx.body;
|
|
1385
|
+
const minPasswordLength = ctx.context.options?.emailAndPassword?.minPasswordLength || 8;
|
|
1386
|
+
if (password.length < minPasswordLength) {
|
|
1387
|
+
ctx.context.logger.error("Password is too short");
|
|
1388
|
+
return ctx.json(null, {
|
|
1389
|
+
status: 400,
|
|
1390
|
+
body: { message: "Password is too short" }
|
|
1391
|
+
});
|
|
552
1392
|
}
|
|
553
|
-
const
|
|
554
|
-
|
|
1393
|
+
const argon2id = new Argon2id3();
|
|
1394
|
+
const dbUser = await ctx.context.internalAdapter.findUserByEmail(email);
|
|
1395
|
+
const hash = await argon2id.hash(password);
|
|
1396
|
+
if (dbUser?.user) {
|
|
1397
|
+
return ctx.json(null, {
|
|
1398
|
+
status: 400,
|
|
1399
|
+
body: {
|
|
1400
|
+
message: "User already exists"
|
|
1401
|
+
}
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
const createdUser = await ctx.context.internalAdapter.createUser({
|
|
1405
|
+
id: generateRandomString3(32, alphabet3("a-z", "0-9", "A-Z")),
|
|
1406
|
+
email: email.toLowerCase(),
|
|
1407
|
+
name,
|
|
1408
|
+
image,
|
|
1409
|
+
emailVerified: false,
|
|
1410
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1411
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1412
|
+
});
|
|
1413
|
+
await ctx.context.internalAdapter.linkAccount({
|
|
1414
|
+
id: generateRandomString3(32, alphabet3("a-z", "0-9", "A-Z")),
|
|
1415
|
+
userId: createdUser.id,
|
|
1416
|
+
providerId: "credential",
|
|
1417
|
+
accountId: createdUser.id,
|
|
1418
|
+
password: hash
|
|
1419
|
+
});
|
|
1420
|
+
const session = await ctx.context.internalAdapter.createSession(
|
|
1421
|
+
createdUser.id,
|
|
1422
|
+
ctx.request
|
|
1423
|
+
);
|
|
1424
|
+
await ctx.setSignedCookie(
|
|
1425
|
+
ctx.context.authCookies.sessionToken.name,
|
|
1426
|
+
session.id,
|
|
1427
|
+
ctx.context.secret,
|
|
1428
|
+
ctx.context.authCookies.sessionToken.options
|
|
1429
|
+
);
|
|
1430
|
+
return ctx.json(
|
|
1431
|
+
{
|
|
1432
|
+
user: createdUser,
|
|
1433
|
+
session
|
|
1434
|
+
},
|
|
1435
|
+
{
|
|
1436
|
+
body: ctx.body.callbackURL ? {
|
|
1437
|
+
url: ctx.body.callbackURL,
|
|
1438
|
+
redirect: true
|
|
1439
|
+
} : {
|
|
1440
|
+
user: createdUser,
|
|
1441
|
+
session
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
);
|
|
555
1445
|
}
|
|
556
|
-
|
|
1446
|
+
);
|
|
1447
|
+
|
|
1448
|
+
// src/api/routes/error.ts
|
|
1449
|
+
var html = (errorCode = "Unknown") => `<!DOCTYPE html>
|
|
1450
|
+
<html lang="en">
|
|
1451
|
+
<head>
|
|
1452
|
+
<meta charset="UTF-8">
|
|
1453
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1454
|
+
<title>Authentication Error</title>
|
|
1455
|
+
<style>
|
|
1456
|
+
:root {
|
|
1457
|
+
--bg-color: #f8f9fa;
|
|
1458
|
+
--text-color: #212529;
|
|
1459
|
+
--accent-color: #000000;
|
|
1460
|
+
--error-color: #dc3545;
|
|
1461
|
+
--border-color: #e9ecef;
|
|
1462
|
+
}
|
|
1463
|
+
body {
|
|
1464
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
1465
|
+
background-color: var(--bg-color);
|
|
1466
|
+
color: var(--text-color);
|
|
1467
|
+
display: flex;
|
|
1468
|
+
justify-content: center;
|
|
1469
|
+
align-items: center;
|
|
1470
|
+
height: 100vh;
|
|
1471
|
+
margin: 0;
|
|
1472
|
+
line-height: 1.5;
|
|
1473
|
+
}
|
|
1474
|
+
.error-container {
|
|
1475
|
+
background-color: #ffffff;
|
|
1476
|
+
border-radius: 12px;
|
|
1477
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
|
1478
|
+
padding: 2.5rem;
|
|
1479
|
+
text-align: center;
|
|
1480
|
+
max-width: 90%;
|
|
1481
|
+
width: 400px;
|
|
1482
|
+
}
|
|
1483
|
+
h1 {
|
|
1484
|
+
color: var(--error-color);
|
|
1485
|
+
font-size: 1.75rem;
|
|
1486
|
+
margin-bottom: 1rem;
|
|
1487
|
+
font-weight: 600;
|
|
1488
|
+
}
|
|
1489
|
+
p {
|
|
1490
|
+
margin-bottom: 1.5rem;
|
|
1491
|
+
color: #495057;
|
|
1492
|
+
}
|
|
1493
|
+
.btn {
|
|
1494
|
+
background-color: var(--accent-color);
|
|
1495
|
+
color: #ffffff;
|
|
1496
|
+
text-decoration: none;
|
|
1497
|
+
padding: 0.75rem 1.5rem;
|
|
1498
|
+
border-radius: 6px;
|
|
1499
|
+
transition: all 0.3s ease;
|
|
1500
|
+
display: inline-block;
|
|
1501
|
+
font-weight: 500;
|
|
1502
|
+
border: 2px solid var(--accent-color);
|
|
1503
|
+
}
|
|
1504
|
+
.btn:hover {
|
|
1505
|
+
background-color: #131721;
|
|
1506
|
+
}
|
|
1507
|
+
.error-code {
|
|
1508
|
+
font-size: 0.875rem;
|
|
1509
|
+
color: #6c757d;
|
|
1510
|
+
margin-top: 1.5rem;
|
|
1511
|
+
padding-top: 1.5rem;
|
|
1512
|
+
border-top: 1px solid var(--border-color);
|
|
1513
|
+
}
|
|
1514
|
+
.icon {
|
|
1515
|
+
font-size: 3rem;
|
|
1516
|
+
margin-bottom: 1rem;
|
|
1517
|
+
}
|
|
1518
|
+
</style>
|
|
1519
|
+
</head>
|
|
1520
|
+
<body>
|
|
1521
|
+
<div class="error-container">
|
|
1522
|
+
<div class="icon">\u26A0\uFE0F</div>
|
|
1523
|
+
<h1>Better Auth Error</h1>
|
|
1524
|
+
<p>We encountered an issue while processing your request. Please try again or contact the application owner if the problem persists.</p>
|
|
1525
|
+
<a href="#" id="returnLink" class="btn">Return to Application</a>
|
|
1526
|
+
<div class="error-code">Error Code: <span id="errorCode">${errorCode}</span></div>
|
|
1527
|
+
</div>
|
|
1528
|
+
</body>
|
|
1529
|
+
</html>`;
|
|
1530
|
+
var error = createAuthEndpoint(
|
|
1531
|
+
"/error",
|
|
1532
|
+
{
|
|
1533
|
+
method: "GET"
|
|
1534
|
+
},
|
|
1535
|
+
async (c) => {
|
|
1536
|
+
const query = new URL(c.request?.url || "").searchParams.get("error") || "Unknown";
|
|
1537
|
+
return new Response(html(query), {
|
|
1538
|
+
headers: {
|
|
1539
|
+
"Content-Type": "text/html"
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
);
|
|
1544
|
+
|
|
1545
|
+
// src/api/index.ts
|
|
1546
|
+
var router = (ctx) => {
|
|
1547
|
+
const pluginEndpoints = ctx.options.plugins?.reduce(
|
|
1548
|
+
(acc, plugin) => {
|
|
1549
|
+
return {
|
|
1550
|
+
...acc,
|
|
1551
|
+
...plugin.endpoints
|
|
1552
|
+
};
|
|
1553
|
+
},
|
|
1554
|
+
{}
|
|
1555
|
+
);
|
|
1556
|
+
const middlewares = ctx.options.plugins?.map(
|
|
1557
|
+
(plugin) => plugin.middlewares?.map((m) => {
|
|
1558
|
+
const middleware = async (context) => {
|
|
1559
|
+
return m.middleware({
|
|
1560
|
+
...context,
|
|
1561
|
+
context: {
|
|
1562
|
+
...ctx,
|
|
1563
|
+
...context.context
|
|
1564
|
+
}
|
|
1565
|
+
});
|
|
1566
|
+
};
|
|
1567
|
+
middleware.path = m.path;
|
|
1568
|
+
middleware.options = m.middleware.options;
|
|
1569
|
+
middleware.headers = m.middleware.headers;
|
|
1570
|
+
return {
|
|
1571
|
+
path: m.path,
|
|
1572
|
+
middleware
|
|
1573
|
+
};
|
|
1574
|
+
})
|
|
1575
|
+
).filter((plugin) => plugin !== void 0).flat() || [];
|
|
1576
|
+
async function typedSession(ctx2) {
|
|
1577
|
+
const handler = await getSession(ctx2);
|
|
1578
|
+
return handler;
|
|
1579
|
+
}
|
|
1580
|
+
typedSession.path = getSession.path;
|
|
1581
|
+
typedSession.method = getSession.method;
|
|
1582
|
+
typedSession.options = getSession.options;
|
|
1583
|
+
typedSession.headers = getSession.headers;
|
|
1584
|
+
const baseEndpoints = {
|
|
1585
|
+
signInOAuth,
|
|
1586
|
+
callbackOAuth,
|
|
1587
|
+
getCSRFToken,
|
|
1588
|
+
getSession: typedSession,
|
|
1589
|
+
signOut,
|
|
1590
|
+
signUpEmail,
|
|
1591
|
+
signInEmail,
|
|
1592
|
+
forgetPassword,
|
|
1593
|
+
resetPassword,
|
|
1594
|
+
verifyEmail,
|
|
1595
|
+
sendVerificationEmail
|
|
1596
|
+
};
|
|
1597
|
+
const endpoints = {
|
|
1598
|
+
...baseEndpoints,
|
|
1599
|
+
...pluginEndpoints,
|
|
1600
|
+
ok,
|
|
1601
|
+
welcome,
|
|
1602
|
+
error
|
|
1603
|
+
};
|
|
1604
|
+
let api = {};
|
|
1605
|
+
for (const [key, value] of Object.entries(endpoints)) {
|
|
1606
|
+
api[key] = async (context) => {
|
|
1607
|
+
for (const plugin of ctx.options.plugins || []) {
|
|
1608
|
+
if (plugin.hooks?.before) {
|
|
1609
|
+
for (const hook of plugin.hooks.before) {
|
|
1610
|
+
const match = hook.matcher({
|
|
1611
|
+
...context,
|
|
1612
|
+
...value
|
|
1613
|
+
});
|
|
1614
|
+
if (match) {
|
|
1615
|
+
const hookRes = await hook.handler(context);
|
|
1616
|
+
if (hookRes && "context" in hookRes) {
|
|
1617
|
+
context = {
|
|
1618
|
+
...context,
|
|
1619
|
+
...hookRes.context,
|
|
1620
|
+
...value
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
const endpointRes = value({
|
|
1628
|
+
...context,
|
|
1629
|
+
context: {
|
|
1630
|
+
...ctx,
|
|
1631
|
+
...context.context
|
|
1632
|
+
}
|
|
1633
|
+
});
|
|
1634
|
+
let response = endpointRes;
|
|
1635
|
+
for (const plugin of ctx.options.plugins || []) {
|
|
1636
|
+
if (plugin.hooks?.after) {
|
|
1637
|
+
for (const hook of plugin.hooks.after) {
|
|
1638
|
+
const match = hook.matcher(context);
|
|
1639
|
+
if (match) {
|
|
1640
|
+
const obj = Object.assign(context, {
|
|
1641
|
+
returned: endpointRes
|
|
1642
|
+
});
|
|
1643
|
+
const hookRes = await hook.handler(obj);
|
|
1644
|
+
if (hookRes && "response" in hookRes) {
|
|
1645
|
+
response = hookRes.response;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
return response;
|
|
1652
|
+
};
|
|
1653
|
+
api[key].path = value.path;
|
|
1654
|
+
api[key].method = value.method;
|
|
1655
|
+
api[key].options = value.options;
|
|
1656
|
+
api[key].headers = value.headers;
|
|
1657
|
+
}
|
|
1658
|
+
return createRouter(api, {
|
|
1659
|
+
extraContext: ctx,
|
|
1660
|
+
basePath: ctx.options.basePath,
|
|
1661
|
+
routerMiddleware: [
|
|
1662
|
+
{
|
|
1663
|
+
path: "/**",
|
|
1664
|
+
middleware: csrfMiddleware
|
|
1665
|
+
},
|
|
1666
|
+
...middlewares
|
|
1667
|
+
],
|
|
1668
|
+
/**
|
|
1669
|
+
* this is to remove any sensitive data from the response
|
|
1670
|
+
*/
|
|
1671
|
+
async transformResponse(res) {
|
|
1672
|
+
let body = {};
|
|
1673
|
+
try {
|
|
1674
|
+
body = await res.json();
|
|
1675
|
+
} catch (e) {
|
|
1676
|
+
return res;
|
|
1677
|
+
}
|
|
1678
|
+
if (body?.user) {
|
|
1679
|
+
body.user = parseUser(ctx.options, body.user);
|
|
1680
|
+
}
|
|
1681
|
+
if (body?.session) {
|
|
1682
|
+
body.session = parseSession(ctx.options, body.session);
|
|
1683
|
+
}
|
|
1684
|
+
if (body?.account) {
|
|
1685
|
+
body.account = parseAccount(ctx.options, body.account);
|
|
1686
|
+
}
|
|
1687
|
+
return new Response(body ? JSON.stringify(body) : null, {
|
|
1688
|
+
headers: res.headers,
|
|
1689
|
+
status: res.status,
|
|
1690
|
+
statusText: res.statusText
|
|
1691
|
+
});
|
|
1692
|
+
},
|
|
1693
|
+
onError(e) {
|
|
1694
|
+
console.log(e);
|
|
1695
|
+
}
|
|
1696
|
+
});
|
|
557
1697
|
};
|
|
558
|
-
var signInHandler = withPlugins(signIn);
|
|
559
1698
|
|
|
560
|
-
// src/
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
1699
|
+
// src/adapters/kysely.ts
|
|
1700
|
+
import Database from "better-sqlite3";
|
|
1701
|
+
import { Kysely } from "kysely";
|
|
1702
|
+
import {
|
|
1703
|
+
MysqlDialect,
|
|
1704
|
+
PostgresDialect,
|
|
1705
|
+
SqliteDialect
|
|
1706
|
+
} from "kysely";
|
|
1707
|
+
import { createPool } from "mysql2";
|
|
1708
|
+
import pg from "pg";
|
|
1709
|
+
var { Pool } = pg;
|
|
1710
|
+
function convertWhere(w) {
|
|
1711
|
+
if (!w)
|
|
1712
|
+
return {
|
|
1713
|
+
and: null,
|
|
1714
|
+
or: null
|
|
1715
|
+
};
|
|
1716
|
+
const and = w?.filter((w2) => w2.connector === "AND" || !w2.connector).reduce(
|
|
1717
|
+
(acc, w2) => ({
|
|
1718
|
+
...acc,
|
|
1719
|
+
[w2.field]: w2.value
|
|
1720
|
+
}),
|
|
1721
|
+
{}
|
|
564
1722
|
);
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
}
|
|
1723
|
+
const or = w?.filter((w2) => w2.connector === "OR").reduce(
|
|
1724
|
+
(acc, w2) => ({
|
|
1725
|
+
...acc,
|
|
1726
|
+
[w2.field]: w2.value
|
|
1727
|
+
}),
|
|
1728
|
+
{}
|
|
1729
|
+
);
|
|
1730
|
+
return {
|
|
1731
|
+
and: Object.keys(and).length ? and : null,
|
|
1732
|
+
or: Object.keys(or).length ? or : null
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
function transformTo(val, fields, transform) {
|
|
1736
|
+
for (const key in val) {
|
|
1737
|
+
if (val[key] === 0 && fields[key]?.type === "boolean" && transform?.boolean) {
|
|
1738
|
+
val[key] = false;
|
|
1739
|
+
}
|
|
1740
|
+
if (val[key] === 1 && fields[key]?.type === "boolean" && transform?.boolean) {
|
|
1741
|
+
val[key] = true;
|
|
1742
|
+
}
|
|
1743
|
+
if (fields[key]?.type === "date") {
|
|
1744
|
+
if (!(val[key] instanceof Date)) {
|
|
1745
|
+
val[key] = new Date(val[key]);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
return val;
|
|
1750
|
+
}
|
|
1751
|
+
function transformFrom(val, transform) {
|
|
1752
|
+
for (const key in val) {
|
|
1753
|
+
if (typeof val[key] === "boolean" && transform?.boolean) {
|
|
1754
|
+
val[key] = val[key] ? 1 : 0;
|
|
1755
|
+
}
|
|
1756
|
+
if (val[key] instanceof Date) {
|
|
1757
|
+
val[key] = val[key].toISOString();
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
return val;
|
|
1761
|
+
}
|
|
1762
|
+
var kyselyAdapter = (db, config) => {
|
|
1763
|
+
return {
|
|
1764
|
+
async create(data) {
|
|
1765
|
+
let { model, data: val, select } = data;
|
|
1766
|
+
if (config?.transform) {
|
|
1767
|
+
val = transformFrom(val, config.transform);
|
|
1768
|
+
}
|
|
1769
|
+
let res = await db.insertInto(model).values(val).returningAll().executeTakeFirst();
|
|
1770
|
+
if (config?.transform) {
|
|
1771
|
+
const schema = config.transform.schema[model];
|
|
1772
|
+
res = schema ? transformTo(val, schema, config.transform) : res;
|
|
1773
|
+
}
|
|
1774
|
+
if (select?.length) {
|
|
1775
|
+
const data2 = res ? select.reduce((acc, cur) => {
|
|
1776
|
+
if (res?.[cur]) {
|
|
1777
|
+
return {
|
|
1778
|
+
...acc,
|
|
1779
|
+
[cur]: res[cur]
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
return acc;
|
|
1783
|
+
}, {}) : null;
|
|
1784
|
+
res = data2;
|
|
1785
|
+
}
|
|
1786
|
+
return res;
|
|
1787
|
+
},
|
|
1788
|
+
async findOne(data) {
|
|
1789
|
+
const { model, where, select } = data;
|
|
1790
|
+
const { and, or } = convertWhere(where);
|
|
1791
|
+
let query = db.selectFrom(model).selectAll();
|
|
1792
|
+
if (or) {
|
|
1793
|
+
query = query.where((eb) => eb.or(or));
|
|
1794
|
+
}
|
|
1795
|
+
if (and) {
|
|
1796
|
+
query = query.where((eb) => eb.and(and));
|
|
1797
|
+
}
|
|
1798
|
+
let res = await query.executeTakeFirst();
|
|
1799
|
+
if (select?.length) {
|
|
1800
|
+
const data2 = res ? select.reduce((acc, cur) => {
|
|
1801
|
+
if (res?.[cur]) {
|
|
1802
|
+
return {
|
|
1803
|
+
...acc,
|
|
1804
|
+
[cur]: res[cur]
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
return acc;
|
|
1808
|
+
}, {}) : null;
|
|
1809
|
+
res = data2;
|
|
1810
|
+
}
|
|
1811
|
+
if (config?.transform) {
|
|
1812
|
+
const schema = config.transform.schema[model];
|
|
1813
|
+
res = res && schema ? transformTo(res, schema, config.transform) : res;
|
|
1814
|
+
return res || null;
|
|
1815
|
+
}
|
|
1816
|
+
return res || null;
|
|
1817
|
+
},
|
|
1818
|
+
async findMany(data) {
|
|
1819
|
+
const { model, where } = data;
|
|
1820
|
+
let query = db.selectFrom(model);
|
|
1821
|
+
const { and, or } = convertWhere(where);
|
|
1822
|
+
if (and) {
|
|
1823
|
+
query = query.where((eb) => eb.and(and));
|
|
1824
|
+
}
|
|
1825
|
+
if (or) {
|
|
1826
|
+
query = query.where((eb) => eb.or(or));
|
|
1827
|
+
}
|
|
1828
|
+
const res = await query.selectAll().execute();
|
|
1829
|
+
if (config?.transform) {
|
|
1830
|
+
const schema = config.transform.schema[model];
|
|
1831
|
+
return schema ? res.map((v) => transformTo(v, schema, config.transform)) : res;
|
|
1832
|
+
}
|
|
1833
|
+
return res;
|
|
1834
|
+
},
|
|
1835
|
+
async update(data) {
|
|
1836
|
+
let { model, where, update: val } = data;
|
|
1837
|
+
const { and, or } = convertWhere(where);
|
|
1838
|
+
if (config?.transform) {
|
|
1839
|
+
val = transformFrom(val, config.transform);
|
|
1840
|
+
}
|
|
1841
|
+
let query = db.updateTable(model).set(val);
|
|
1842
|
+
if (and) {
|
|
1843
|
+
query = query.where((eb) => eb.and(and));
|
|
1844
|
+
}
|
|
1845
|
+
if (or) {
|
|
1846
|
+
query = query.where((eb) => eb.or(or));
|
|
1847
|
+
}
|
|
1848
|
+
const res = await query.returningAll().executeTakeFirst();
|
|
1849
|
+
if (config?.transform) {
|
|
1850
|
+
const schema = config.transform.schema[model];
|
|
1851
|
+
return schema ? transformTo(res, schema, config.transform) : res;
|
|
1852
|
+
}
|
|
1853
|
+
return res;
|
|
1854
|
+
},
|
|
1855
|
+
async delete(data) {
|
|
1856
|
+
const { model, where } = data;
|
|
1857
|
+
const { and, or } = convertWhere(where);
|
|
1858
|
+
let query = db.deleteFrom(model);
|
|
1859
|
+
if (and) {
|
|
1860
|
+
query = query.where((eb) => eb.and(and));
|
|
1861
|
+
}
|
|
1862
|
+
if (or) {
|
|
1863
|
+
query = query.where((eb) => eb.or(or));
|
|
1864
|
+
}
|
|
1865
|
+
await query.execute();
|
|
1866
|
+
}
|
|
1867
|
+
};
|
|
1868
|
+
};
|
|
1869
|
+
var getDialect = (config) => {
|
|
1870
|
+
if (!config.database) {
|
|
1871
|
+
return null;
|
|
1872
|
+
}
|
|
1873
|
+
let dialect = null;
|
|
1874
|
+
if ("provider" in config.database) {
|
|
1875
|
+
const provider = config.database.provider;
|
|
1876
|
+
const connectionString = config.database.url.trim();
|
|
1877
|
+
if (provider === "postgres") {
|
|
1878
|
+
const pool = new Pool({
|
|
1879
|
+
connectionString
|
|
1880
|
+
});
|
|
1881
|
+
dialect = new PostgresDialect({
|
|
1882
|
+
pool
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
if (provider === "mysql") {
|
|
1886
|
+
const params = new URL(connectionString);
|
|
1887
|
+
const pool = createPool({
|
|
1888
|
+
host: params.hostname,
|
|
1889
|
+
user: params.username,
|
|
1890
|
+
password: params.password,
|
|
1891
|
+
database: params.pathname.split("/")[1],
|
|
1892
|
+
port: Number(params.port)
|
|
1893
|
+
});
|
|
1894
|
+
dialect = new MysqlDialect({ pool });
|
|
1895
|
+
}
|
|
1896
|
+
if (provider === "sqlite") {
|
|
1897
|
+
const db = new Database(connectionString);
|
|
1898
|
+
dialect = new SqliteDialect({
|
|
1899
|
+
database: db
|
|
1900
|
+
});
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
return dialect;
|
|
1904
|
+
};
|
|
1905
|
+
var createKyselyAdapter = (config) => {
|
|
1906
|
+
const dialect = getDialect(config);
|
|
1907
|
+
if (!dialect) {
|
|
1908
|
+
return null;
|
|
1909
|
+
}
|
|
1910
|
+
const db = new Kysely({
|
|
1911
|
+
dialect
|
|
1912
|
+
});
|
|
1913
|
+
return db;
|
|
1914
|
+
};
|
|
1915
|
+
var getDatabaseType = (config) => {
|
|
1916
|
+
if ("provider" in config.database) {
|
|
1917
|
+
return config.database.provider;
|
|
1918
|
+
}
|
|
1919
|
+
if ("dialect" in config.database) {
|
|
1920
|
+
if (config.database.dialect instanceof PostgresDialect) {
|
|
1921
|
+
return "postgres";
|
|
1922
|
+
}
|
|
1923
|
+
if (config.database.dialect instanceof MysqlDialect) {
|
|
1924
|
+
return "mysql";
|
|
1925
|
+
}
|
|
1926
|
+
if (config.database.dialect instanceof SqliteDialect) {
|
|
1927
|
+
return "sqlite";
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
return "sqlite";
|
|
1931
|
+
};
|
|
1932
|
+
|
|
1933
|
+
// src/adapters/get-tables.ts
|
|
1934
|
+
var getAuthTables = (options) => {
|
|
1935
|
+
const pluginSchema = options.plugins?.reduce((acc, plugin) => {
|
|
1936
|
+
const schema = plugin.schema;
|
|
1937
|
+
return {
|
|
1938
|
+
...acc,
|
|
1939
|
+
...schema
|
|
1940
|
+
};
|
|
1941
|
+
}, {});
|
|
1942
|
+
return {
|
|
1943
|
+
...pluginSchema,
|
|
1944
|
+
user: {
|
|
1945
|
+
tableName: options.user?.modelName || "user",
|
|
1946
|
+
fields: {
|
|
1947
|
+
name: {
|
|
1948
|
+
type: "string"
|
|
1949
|
+
},
|
|
1950
|
+
email: {
|
|
1951
|
+
type: "string"
|
|
1952
|
+
},
|
|
1953
|
+
emailVerified: {
|
|
1954
|
+
type: "boolean",
|
|
1955
|
+
defaultValue: () => false
|
|
1956
|
+
},
|
|
1957
|
+
image: {
|
|
1958
|
+
type: "string",
|
|
1959
|
+
required: false
|
|
1960
|
+
},
|
|
1961
|
+
createdAt: {
|
|
1962
|
+
type: "date",
|
|
1963
|
+
defaultValue: () => /* @__PURE__ */ new Date()
|
|
1964
|
+
},
|
|
1965
|
+
updatedAt: {
|
|
1966
|
+
type: "date",
|
|
1967
|
+
defaultValue: () => /* @__PURE__ */ new Date()
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
},
|
|
1971
|
+
session: {
|
|
1972
|
+
tableName: options.session?.modelName || "session",
|
|
1973
|
+
fields: {
|
|
1974
|
+
expiresAt: {
|
|
1975
|
+
type: "date"
|
|
1976
|
+
},
|
|
1977
|
+
ipAddress: {
|
|
1978
|
+
type: "string",
|
|
1979
|
+
required: false
|
|
1980
|
+
},
|
|
1981
|
+
userAgent: {
|
|
1982
|
+
type: "string",
|
|
1983
|
+
required: false
|
|
1984
|
+
},
|
|
1985
|
+
userId: {
|
|
1986
|
+
type: "string",
|
|
1987
|
+
references: {
|
|
1988
|
+
model: "user",
|
|
1989
|
+
field: "id",
|
|
1990
|
+
onDelete: "cascade"
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
},
|
|
1995
|
+
account: {
|
|
1996
|
+
tableName: options.account?.modelName || "account",
|
|
1997
|
+
fields: {
|
|
1998
|
+
accountId: {
|
|
1999
|
+
type: "string"
|
|
2000
|
+
},
|
|
2001
|
+
providerId: {
|
|
2002
|
+
type: "string"
|
|
2003
|
+
},
|
|
2004
|
+
userId: {
|
|
2005
|
+
type: "string",
|
|
2006
|
+
references: {
|
|
2007
|
+
model: "user",
|
|
2008
|
+
field: "id",
|
|
2009
|
+
onDelete: "cascade"
|
|
2010
|
+
}
|
|
2011
|
+
},
|
|
2012
|
+
accessToken: {
|
|
2013
|
+
type: "string",
|
|
2014
|
+
required: false
|
|
2015
|
+
},
|
|
2016
|
+
refreshToken: {
|
|
2017
|
+
type: "string",
|
|
2018
|
+
required: false
|
|
2019
|
+
},
|
|
2020
|
+
idToken: {
|
|
2021
|
+
type: "string",
|
|
2022
|
+
required: false
|
|
2023
|
+
},
|
|
2024
|
+
accessTokenExpiresAt: {
|
|
2025
|
+
type: "date",
|
|
2026
|
+
required: false
|
|
2027
|
+
},
|
|
2028
|
+
refreshTokenExpiresAt: {
|
|
2029
|
+
type: "date",
|
|
2030
|
+
required: false
|
|
2031
|
+
},
|
|
2032
|
+
password: {
|
|
2033
|
+
type: "string",
|
|
2034
|
+
required: false
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
570
2037
|
}
|
|
571
|
-
}
|
|
572
|
-
return {
|
|
573
|
-
status: 200
|
|
574
2038
|
};
|
|
575
2039
|
};
|
|
576
|
-
var signOutHandler = withPlugins(signOut);
|
|
577
2040
|
|
|
578
|
-
// src/
|
|
579
|
-
function
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
* client sdk for sign in instead unless you
|
|
588
|
-
* have a good reason to use this.
|
|
589
|
-
*/
|
|
590
|
-
signIn: async (request, input) => {
|
|
591
|
-
const url = request instanceof Headers ? request.get("referer") : request.url;
|
|
592
|
-
const req = new Request(url, {
|
|
593
|
-
body: JSON.stringify(input),
|
|
594
|
-
method: "POST"
|
|
595
|
-
});
|
|
596
|
-
const context = await toContext(options, req);
|
|
597
|
-
context.disableCSRF = true;
|
|
598
|
-
context.request.body = {
|
|
599
|
-
currentURL: url,
|
|
600
|
-
provider: input.provider
|
|
601
|
-
};
|
|
602
|
-
const response = await signInHandler(context);
|
|
603
|
-
if (response.body.redirect) {
|
|
604
|
-
return toResponse(
|
|
605
|
-
{
|
|
606
|
-
status: 302,
|
|
607
|
-
headers: {
|
|
608
|
-
Location: response.body.url
|
|
609
|
-
}
|
|
610
|
-
},
|
|
611
|
-
context
|
|
612
|
-
);
|
|
613
|
-
}
|
|
614
|
-
return toResponse(response, context, handlerOptions);
|
|
615
|
-
},
|
|
616
|
-
/**
|
|
617
|
-
* Get the current logged in user session.
|
|
618
|
-
*/
|
|
619
|
-
getSession: async (request) => {
|
|
620
|
-
const url = request instanceof Headers ? request.get("referer") || request.get("x-forwarded-host") || "http://localhost" : request.url;
|
|
621
|
-
const req = request instanceof Request ? request : new Request(url, {
|
|
622
|
-
method: "POST",
|
|
623
|
-
body: JSON.stringify({}),
|
|
624
|
-
headers: request
|
|
625
|
-
});
|
|
626
|
-
const context = await toContext(options, req);
|
|
627
|
-
context.disableCSRF = true;
|
|
628
|
-
const response = await getServerSession(context);
|
|
629
|
-
return response;
|
|
630
|
-
},
|
|
631
|
-
/**
|
|
632
|
-
* Signout the current user.
|
|
633
|
-
* Delete the session and clear the cookies.
|
|
634
|
-
*/
|
|
635
|
-
signOut: async (request) => {
|
|
636
|
-
const url = request instanceof Headers ? request.get("referer") : request.url;
|
|
637
|
-
const req = request instanceof Request ? request : new Request(url, {
|
|
638
|
-
method: "POST",
|
|
639
|
-
body: JSON.stringify({}),
|
|
640
|
-
headers: request
|
|
641
|
-
});
|
|
642
|
-
const context = await toContext(options, req);
|
|
643
|
-
context.disableCSRF = true;
|
|
644
|
-
const response = await signOutHandler(context);
|
|
645
|
-
return toResponse(response, context, handlerOptions);
|
|
2041
|
+
// src/adapters/utils.ts
|
|
2042
|
+
function getAdapter(options) {
|
|
2043
|
+
if (!options.database) {
|
|
2044
|
+
throw new BetterAuthError("Database configuration is required");
|
|
2045
|
+
}
|
|
2046
|
+
if ("provider" in options.database) {
|
|
2047
|
+
const db = createKyselyAdapter(options);
|
|
2048
|
+
if (!db) {
|
|
2049
|
+
throw new BetterAuthError("Failed to initialize database adapter");
|
|
646
2050
|
}
|
|
647
|
-
|
|
648
|
-
|
|
2051
|
+
const tables = getAuthTables(options);
|
|
2052
|
+
return kyselyAdapter(db, {
|
|
2053
|
+
transform: {
|
|
2054
|
+
schema: {
|
|
2055
|
+
[tables.user.tableName]: tables.user.fields,
|
|
2056
|
+
[tables.session.tableName]: tables.session.fields,
|
|
2057
|
+
[tables.account.tableName]: tables.account.fields
|
|
2058
|
+
},
|
|
2059
|
+
date: true,
|
|
2060
|
+
boolean: getDatabaseType(options) === "sqlite"
|
|
2061
|
+
}
|
|
2062
|
+
});
|
|
2063
|
+
}
|
|
2064
|
+
return options.database;
|
|
649
2065
|
}
|
|
650
2066
|
|
|
651
|
-
// src/
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
switch (unit) {
|
|
656
|
-
case "s":
|
|
657
|
-
return timeInMs;
|
|
658
|
-
case "m":
|
|
659
|
-
return timeInMs * 60;
|
|
660
|
-
case "hr":
|
|
661
|
-
return timeInMs * 60 * 60;
|
|
662
|
-
case "d":
|
|
663
|
-
return timeInMs * 60 * 60 * 24;
|
|
664
|
-
case "w":
|
|
665
|
-
return timeInMs * 60 * 60 * 24 * 7;
|
|
666
|
-
default:
|
|
667
|
-
return 0;
|
|
668
|
-
}
|
|
669
|
-
};
|
|
2067
|
+
// src/adapters/internal-adapter.ts
|
|
2068
|
+
import { alphabet as alphabet4, generateRandomString as generateRandomString4 } from "oslo/crypto";
|
|
2069
|
+
|
|
2070
|
+
// src/utils/date.ts
|
|
670
2071
|
var getDate = (span) => {
|
|
671
|
-
const sec = typeof span === "number" ? span : timeSpan(span);
|
|
672
2072
|
const date = /* @__PURE__ */ new Date();
|
|
673
|
-
return new Date(date.getTime() +
|
|
2073
|
+
return new Date(date.getTime() + span);
|
|
674
2074
|
};
|
|
675
2075
|
|
|
676
2076
|
// src/adapters/internal-adapter.ts
|
|
677
|
-
var createInternalAdapter = (
|
|
2077
|
+
var createInternalAdapter = (adapter, options) => {
|
|
2078
|
+
const sessionExpiration = options.session?.expiresIn || 60 * 60 * 24 * 7;
|
|
2079
|
+
const tables = getAuthTables(options);
|
|
678
2080
|
return {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
2081
|
+
createOAuthUser: async (user, account) => {
|
|
2082
|
+
try {
|
|
2083
|
+
const createdUser = await adapter.create({
|
|
2084
|
+
model: tables.user.tableName,
|
|
2085
|
+
data: user
|
|
2086
|
+
});
|
|
2087
|
+
const createdAccount = await adapter.create({
|
|
2088
|
+
model: tables.account.tableName,
|
|
2089
|
+
data: account
|
|
684
2090
|
});
|
|
2091
|
+
return {
|
|
2092
|
+
user: createdUser,
|
|
2093
|
+
account: createdAccount
|
|
2094
|
+
};
|
|
2095
|
+
} catch (e) {
|
|
2096
|
+
console.log(e);
|
|
2097
|
+
return null;
|
|
685
2098
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
2099
|
+
},
|
|
2100
|
+
createUser: async (user) => {
|
|
2101
|
+
const createdUser = await adapter.create({
|
|
2102
|
+
model: tables.user.tableName,
|
|
2103
|
+
data: user
|
|
2104
|
+
});
|
|
2105
|
+
return createdUser;
|
|
2106
|
+
},
|
|
2107
|
+
createSession: async (userId, request) => {
|
|
2108
|
+
const data = {
|
|
2109
|
+
id: generateRandomString4(32, alphabet4("a-z", "0-9", "A-Z")),
|
|
2110
|
+
userId,
|
|
2111
|
+
expiresAt: getDate(sessionExpiration),
|
|
2112
|
+
ipAddress: request?.headers.get("x-forwarded-for") || "",
|
|
2113
|
+
userAgent: request?.headers.get("user-agent") || ""
|
|
2114
|
+
};
|
|
2115
|
+
const session = adapter.create({
|
|
2116
|
+
model: tables.session.tableName,
|
|
2117
|
+
data
|
|
2118
|
+
});
|
|
2119
|
+
return session;
|
|
2120
|
+
},
|
|
2121
|
+
findSession: async (sessionId) => {
|
|
2122
|
+
const session = await adapter.findOne({
|
|
2123
|
+
model: tables.session.tableName,
|
|
2124
|
+
where: [
|
|
2125
|
+
{
|
|
2126
|
+
value: sessionId,
|
|
2127
|
+
field: "id"
|
|
2128
|
+
}
|
|
2129
|
+
]
|
|
2130
|
+
});
|
|
2131
|
+
if (!session) {
|
|
2132
|
+
return null;
|
|
2133
|
+
}
|
|
2134
|
+
const user = await adapter.findOne({
|
|
2135
|
+
model: tables.user.tableName,
|
|
2136
|
+
where: [
|
|
2137
|
+
{
|
|
2138
|
+
value: session.userId,
|
|
2139
|
+
field: "id"
|
|
2140
|
+
}
|
|
2141
|
+
]
|
|
694
2142
|
});
|
|
695
|
-
|
|
2143
|
+
if (!user) {
|
|
2144
|
+
return null;
|
|
2145
|
+
}
|
|
2146
|
+
return {
|
|
2147
|
+
session,
|
|
2148
|
+
user
|
|
2149
|
+
};
|
|
696
2150
|
},
|
|
697
|
-
updateSession: async (
|
|
698
|
-
const
|
|
699
|
-
const
|
|
700
|
-
const
|
|
2151
|
+
updateSession: async (session) => {
|
|
2152
|
+
const updateAge = options.session?.updateAge === void 0 ? 1e3 : options.session?.updateAge;
|
|
2153
|
+
const updateDate = updateAge === 0 ? 0 : getDate(updateAge).valueOf();
|
|
2154
|
+
const maxAge = getDate(sessionExpiration);
|
|
2155
|
+
const shouldBeUpdated = session.expiresAt.valueOf() - maxAge.valueOf() + updateDate <= Date.now();
|
|
701
2156
|
if (shouldBeUpdated) {
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
2157
|
+
const updatedSession = await adapter.create({
|
|
2158
|
+
model: tables.session.tableName,
|
|
2159
|
+
data: {
|
|
2160
|
+
...session,
|
|
2161
|
+
id: generateRandomString4(32, alphabet4("a-z", "0-9", "A-Z")),
|
|
2162
|
+
expiresAt: new Date(Date.now() + sessionExpiration)
|
|
2163
|
+
}
|
|
2164
|
+
});
|
|
2165
|
+
await adapter.update({
|
|
2166
|
+
model: tables.session.tableName,
|
|
711
2167
|
where: [
|
|
712
2168
|
{
|
|
713
2169
|
field: "id",
|
|
714
|
-
value:
|
|
2170
|
+
value: session.id
|
|
715
2171
|
}
|
|
716
2172
|
],
|
|
717
2173
|
update: {
|
|
718
|
-
|
|
2174
|
+
/**
|
|
2175
|
+
* update the session to expire in 2 minute. This is to prevent
|
|
2176
|
+
* the session from expiring too quickly and logging the user out.
|
|
2177
|
+
*/
|
|
2178
|
+
expiresAt: new Date(Date.now() + 1e3 * 60 * 2)
|
|
719
2179
|
}
|
|
720
2180
|
});
|
|
721
2181
|
return updatedSession;
|
|
722
2182
|
}
|
|
723
|
-
return
|
|
2183
|
+
return session;
|
|
724
2184
|
},
|
|
725
|
-
deleteSession: async (id
|
|
726
|
-
const
|
|
727
|
-
model:
|
|
2185
|
+
deleteSession: async (id) => {
|
|
2186
|
+
const session = await adapter.delete({
|
|
2187
|
+
model: tables.session.tableName,
|
|
728
2188
|
where: [
|
|
729
2189
|
{
|
|
730
2190
|
field: "id",
|
|
@@ -732,110 +2192,80 @@ var createInternalAdapter = (db) => {
|
|
|
732
2192
|
}
|
|
733
2193
|
]
|
|
734
2194
|
});
|
|
735
|
-
return
|
|
2195
|
+
return session;
|
|
736
2196
|
},
|
|
737
|
-
|
|
738
|
-
const user = await
|
|
739
|
-
model:
|
|
740
|
-
data: {
|
|
741
|
-
id: generateRandomString(32),
|
|
742
|
-
...data.user
|
|
743
|
-
},
|
|
744
|
-
select: context.user.selectFields
|
|
745
|
-
});
|
|
746
|
-
const account = await db.create({
|
|
747
|
-
model: context.account.modelName,
|
|
748
|
-
data: {
|
|
749
|
-
...data.account,
|
|
750
|
-
userId: user.id,
|
|
751
|
-
providerId: data.account.providerId.toString(),
|
|
752
|
-
accountId: data.account.accountId.toString()
|
|
753
|
-
}
|
|
754
|
-
});
|
|
755
|
-
return { user, account };
|
|
756
|
-
},
|
|
757
|
-
updateUserByEmail: async (email, data, context) => {
|
|
758
|
-
const user = await db.update({
|
|
759
|
-
model: context.user.modelName,
|
|
2197
|
+
findUserByEmail: async (email) => {
|
|
2198
|
+
const user = await adapter.findOne({
|
|
2199
|
+
model: tables.user.tableName,
|
|
760
2200
|
where: [
|
|
761
2201
|
{
|
|
762
|
-
|
|
763
|
-
|
|
2202
|
+
value: email.toLowerCase(),
|
|
2203
|
+
field: "email"
|
|
764
2204
|
}
|
|
765
|
-
]
|
|
766
|
-
update: data
|
|
2205
|
+
]
|
|
767
2206
|
});
|
|
768
|
-
return
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
const user = await db.findOne({
|
|
772
|
-
model: context.user.modelName,
|
|
2207
|
+
if (!user) return null;
|
|
2208
|
+
const accounts = await adapter.findMany({
|
|
2209
|
+
model: tables.account.tableName,
|
|
773
2210
|
where: [
|
|
774
2211
|
{
|
|
775
|
-
|
|
776
|
-
|
|
2212
|
+
value: user.id,
|
|
2213
|
+
field: "userId"
|
|
777
2214
|
}
|
|
778
|
-
]
|
|
779
|
-
select: context.user.selectFields
|
|
2215
|
+
]
|
|
780
2216
|
});
|
|
781
|
-
return
|
|
2217
|
+
return {
|
|
2218
|
+
user,
|
|
2219
|
+
accounts
|
|
2220
|
+
};
|
|
782
2221
|
},
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
userId: id
|
|
787
|
-
});
|
|
788
|
-
}
|
|
789
|
-
const session2 = await db.findOne({
|
|
790
|
-
model: context.session.modelName,
|
|
2222
|
+
findUserById: async (userId) => {
|
|
2223
|
+
const user = await adapter.findOne({
|
|
2224
|
+
model: tables.user.tableName,
|
|
791
2225
|
where: [
|
|
792
2226
|
{
|
|
793
2227
|
field: "id",
|
|
794
|
-
value:
|
|
2228
|
+
value: userId
|
|
795
2229
|
}
|
|
796
|
-
]
|
|
797
|
-
|
|
2230
|
+
]
|
|
2231
|
+
});
|
|
2232
|
+
return user;
|
|
2233
|
+
},
|
|
2234
|
+
linkAccount: async (account) => {
|
|
2235
|
+
const _account = await adapter.create({
|
|
2236
|
+
model: tables.account.tableName,
|
|
2237
|
+
data: account
|
|
798
2238
|
});
|
|
799
|
-
return
|
|
2239
|
+
return _account;
|
|
800
2240
|
},
|
|
801
|
-
|
|
802
|
-
const user = await
|
|
803
|
-
model:
|
|
2241
|
+
updateUserByEmail: async (email, data) => {
|
|
2242
|
+
const user = await adapter.update({
|
|
2243
|
+
model: tables.user.tableName,
|
|
804
2244
|
where: [
|
|
805
2245
|
{
|
|
806
|
-
|
|
807
|
-
|
|
2246
|
+
value: email,
|
|
2247
|
+
field: "email"
|
|
808
2248
|
}
|
|
809
2249
|
],
|
|
810
|
-
|
|
2250
|
+
update: data
|
|
811
2251
|
});
|
|
812
2252
|
return user;
|
|
813
2253
|
},
|
|
814
|
-
|
|
815
|
-
const account = await
|
|
816
|
-
model:
|
|
2254
|
+
updatePassword: async (userId, password) => {
|
|
2255
|
+
const account = await adapter.update({
|
|
2256
|
+
model: tables.account.tableName,
|
|
817
2257
|
where: [
|
|
818
2258
|
{
|
|
819
|
-
|
|
820
|
-
|
|
2259
|
+
value: userId,
|
|
2260
|
+
field: "userId"
|
|
821
2261
|
},
|
|
822
2262
|
{
|
|
823
|
-
field: "
|
|
824
|
-
value:
|
|
2263
|
+
field: "providerId",
|
|
2264
|
+
value: "credential"
|
|
825
2265
|
}
|
|
826
2266
|
],
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
return account;
|
|
830
|
-
},
|
|
831
|
-
linkAccount: async (input, context) => {
|
|
832
|
-
const { userId, providerId, accountId } = input;
|
|
833
|
-
const account = await db.create({
|
|
834
|
-
model: context.account.modelName,
|
|
835
|
-
data: {
|
|
836
|
-
userId,
|
|
837
|
-
providerId: providerId.toString(),
|
|
838
|
-
accountId: accountId.toString()
|
|
2267
|
+
update: {
|
|
2268
|
+
password
|
|
839
2269
|
}
|
|
840
2270
|
});
|
|
841
2271
|
return account;
|
|
@@ -843,51 +2273,21 @@ var createInternalAdapter = (db) => {
|
|
|
843
2273
|
};
|
|
844
2274
|
};
|
|
845
2275
|
|
|
846
|
-
// src/
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
const { type, required, returned, hashValue, transform } = fields[field];
|
|
852
|
-
internalFields[field] = {
|
|
853
|
-
required: required ?? false,
|
|
854
|
-
returned: returned ?? true,
|
|
855
|
-
hashValue: hashValue ?? false,
|
|
856
|
-
validator: z2[type]().transform(transform || ((x) => x))
|
|
857
|
-
};
|
|
858
|
-
}
|
|
859
|
-
return internalFields;
|
|
860
|
-
}
|
|
861
|
-
function getSelectFields(fields, table) {
|
|
862
|
-
const select = Object.keys(fields).filter((column) => {
|
|
863
|
-
return fields[column]?.returned !== false;
|
|
864
|
-
});
|
|
865
|
-
const defaultSelect = {
|
|
866
|
-
session: ["id", "userId", "expiresAt"],
|
|
867
|
-
user: ["id", "email", "emailVerified"],
|
|
868
|
-
account: ["providerId", "accountId", "userId"]
|
|
2276
|
+
// src/db/field.ts
|
|
2277
|
+
var createFieldAttribute = (type, config) => {
|
|
2278
|
+
return {
|
|
2279
|
+
type,
|
|
2280
|
+
...config
|
|
869
2281
|
};
|
|
870
|
-
return [...defaultSelect[table], ...select];
|
|
871
|
-
}
|
|
872
|
-
var parseUser = (data, context) => {
|
|
873
|
-
const user = Object.keys(data).map((key) => {
|
|
874
|
-
const parsed = context.user.fields[key]?.validator.safeParse(data?.[key]);
|
|
875
|
-
return { key, value: parsed?.success ? parsed.data : data?.[key] };
|
|
876
|
-
}).reduce(
|
|
877
|
-
(acc, { key, value }) => {
|
|
878
|
-
acc[key] = value;
|
|
879
|
-
return acc;
|
|
880
|
-
},
|
|
881
|
-
{}
|
|
882
|
-
);
|
|
883
|
-
return user;
|
|
884
2282
|
};
|
|
885
2283
|
|
|
886
|
-
// src/
|
|
2284
|
+
// src/utils/cookies.ts
|
|
2285
|
+
import { TimeSpan as TimeSpan3 } from "oslo";
|
|
887
2286
|
function getCookies(options) {
|
|
888
|
-
const secure = !!options.advanced?.useSecureCookies;
|
|
2287
|
+
const secure = !!options.advanced?.useSecureCookies || process.env.NODE_ENV === "production";
|
|
889
2288
|
const secureCookiePrefix = secure ? "__Secure-" : "";
|
|
890
2289
|
const cookiePrefix = "better-auth";
|
|
2290
|
+
const sessionMaxAge = new TimeSpan3(7, "d").seconds();
|
|
891
2291
|
return {
|
|
892
2292
|
sessionToken: {
|
|
893
2293
|
name: `${secureCookiePrefix}${cookiePrefix}.session_token`,
|
|
@@ -896,17 +2296,17 @@ function getCookies(options) {
|
|
|
896
2296
|
sameSite: "lax",
|
|
897
2297
|
path: "/",
|
|
898
2298
|
secure,
|
|
899
|
-
maxAge:
|
|
900
|
-
...options.advanced?.sessionCookie
|
|
2299
|
+
maxAge: sessionMaxAge
|
|
901
2300
|
}
|
|
902
2301
|
},
|
|
903
2302
|
csrfToken: {
|
|
904
|
-
name: `${secureCookiePrefix}${cookiePrefix}.csrf_token`,
|
|
2303
|
+
name: `${secureCookiePrefix ? "__Host-" : ""}${cookiePrefix}.csrf_token`,
|
|
905
2304
|
options: {
|
|
906
2305
|
httpOnly: true,
|
|
907
2306
|
sameSite: "lax",
|
|
908
2307
|
path: "/",
|
|
909
|
-
secure
|
|
2308
|
+
secure,
|
|
2309
|
+
maxAge: 60 * 60 * 24 * 7
|
|
910
2310
|
}
|
|
911
2311
|
},
|
|
912
2312
|
state: {
|
|
@@ -944,496 +2344,100 @@ function getCookies(options) {
|
|
|
944
2344
|
}
|
|
945
2345
|
};
|
|
946
2346
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
import { base64url as base64url2 } from "jose";
|
|
953
|
-
async function getTokens(context, provider) {
|
|
954
|
-
const redirectURL = provider.params.redirectURL || `${context.baseURL}${context.basePath}/callback/${provider.id}`;
|
|
955
|
-
const headers = new Headers();
|
|
956
|
-
headers.set("Content-Type", "application/x-www-form-urlencoded");
|
|
957
|
-
headers.set("Accept", "application/json");
|
|
958
|
-
headers.set("User-Agent", "better-auth");
|
|
959
|
-
const encodedCredentials = base64url2.encode(
|
|
960
|
-
`${provider.params.clientId}:${provider.params.clientSecret}`
|
|
961
|
-
);
|
|
962
|
-
headers.set("Authorization", `Basic ${encodedCredentials}`);
|
|
963
|
-
const body = new URLSearchParams();
|
|
964
|
-
body.set("grant_type", "authorization_code");
|
|
965
|
-
body.set("code", context.request.query.code);
|
|
966
|
-
body.set("redirect_uri", redirectURL);
|
|
967
|
-
if (provider.pkCodeVerifier) {
|
|
968
|
-
const codeVerifier = context.request.cookies.get(
|
|
969
|
-
context.cookies.pkCodeVerifier.name
|
|
970
|
-
);
|
|
971
|
-
codeVerifier && body.set("code_verifier", codeVerifier);
|
|
972
|
-
}
|
|
973
|
-
body.set("client_id", provider.params.clientId);
|
|
974
|
-
body.set("client_secret", provider.params.clientSecret);
|
|
975
|
-
let url = provider.params.tokenEndpoint;
|
|
976
|
-
if (!url) {
|
|
977
|
-
const discovery = await discoveryRequest(context, provider);
|
|
978
|
-
if (!discovery.token_endpoint) {
|
|
979
|
-
throw new Error("Missing token endpoint");
|
|
980
|
-
}
|
|
981
|
-
url = discovery.token_endpoint;
|
|
982
|
-
}
|
|
983
|
-
const response = await fetch(url, {
|
|
984
|
-
method: "POST",
|
|
985
|
-
body,
|
|
986
|
-
headers
|
|
987
|
-
});
|
|
988
|
-
const data = await response.json();
|
|
989
|
-
return data;
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
// src/routes/callback.ts
|
|
993
|
-
var callbackQuerySchema = z3.object({
|
|
994
|
-
code: z3.string(),
|
|
995
|
-
state: z3.string(),
|
|
996
|
-
provider: z3.string()
|
|
997
|
-
});
|
|
998
|
-
var callback = async (context) => {
|
|
999
|
-
const parsedQuery = callbackQuerySchema.safeParse(context.request.query);
|
|
1000
|
-
if (!parsedQuery.success) {
|
|
1001
|
-
const error = context.request.url.searchParams.get("error");
|
|
1002
|
-
const errorDesc = context.request.url.searchParams.get("error_description");
|
|
1003
|
-
const state = context.request.url.searchParams.get("state");
|
|
1004
|
-
if (!state) {
|
|
1005
|
-
throw new ProviderError(
|
|
1006
|
-
`state is not returned from ${context.request.url.searchParams.get(
|
|
1007
|
-
"provider"
|
|
1008
|
-
)}`
|
|
1009
|
-
);
|
|
1010
|
-
}
|
|
1011
|
-
const { currentURL } = getState(state);
|
|
2347
|
+
function createCookieGetter(options) {
|
|
2348
|
+
const secure = !!options.advanced?.useSecureCookies || process.env.NODE_ENV === "production";
|
|
2349
|
+
const secureCookiePrefix = secure ? "__Secure-" : "";
|
|
2350
|
+
const cookiePrefix = "better-auth";
|
|
2351
|
+
function getCookie(cookieName, options2) {
|
|
1012
2352
|
return {
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
const storedState = context.request.cookies.get(context.cookies.state.name);
|
|
1022
|
-
const state = parsedQuery.data.state;
|
|
1023
|
-
const { currentURL } = getState(state);
|
|
1024
|
-
if (storedState !== state) {
|
|
1025
|
-
return {
|
|
1026
|
-
status: 302,
|
|
1027
|
-
headers: {
|
|
1028
|
-
Location: `${currentURL}?error=invalid_state`
|
|
1029
|
-
}
|
|
1030
|
-
};
|
|
1031
|
-
}
|
|
1032
|
-
const tokens = await getTokens(context, provider);
|
|
1033
|
-
if (tokens.error) {
|
|
1034
|
-
return {
|
|
1035
|
-
status: 302,
|
|
1036
|
-
headers: {
|
|
1037
|
-
Location: `${currentURL}?error=${tokens.error}`
|
|
1038
|
-
}
|
|
1039
|
-
};
|
|
1040
|
-
}
|
|
1041
|
-
if (provider.type === "oauth" || provider.type === "oidc") {
|
|
1042
|
-
const profile = await provider.getUserInfo(tokens);
|
|
1043
|
-
const {
|
|
1044
|
-
callbackURL,
|
|
1045
|
-
currentURL: currentURL2,
|
|
1046
|
-
signUp: { data, autoCreateSession, onlySignUp }
|
|
1047
|
-
} = getState(state);
|
|
1048
|
-
let userAccount = await context.adapter.findAccount(
|
|
1049
|
-
{
|
|
1050
|
-
providerId: provider.id,
|
|
1051
|
-
accountId: profile.id
|
|
1052
|
-
},
|
|
1053
|
-
context
|
|
1054
|
-
);
|
|
1055
|
-
if (provider.type === "oidc") {
|
|
1056
|
-
if (profile.nonce) {
|
|
1057
|
-
const nonce = context.request.cookies.get(context.cookies.nonce.name);
|
|
1058
|
-
if (profile.nonce !== nonce) {
|
|
1059
|
-
return {
|
|
1060
|
-
status: 302,
|
|
1061
|
-
headers: {
|
|
1062
|
-
Location: `${currentURL2}?error=invalid_nonce`
|
|
1063
|
-
}
|
|
1064
|
-
};
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
if (onlySignUp && userAccount) {
|
|
1069
|
-
return {
|
|
1070
|
-
status: 302,
|
|
1071
|
-
headers: {
|
|
1072
|
-
Location: `${currentURL2}?error=user_already_exist`
|
|
1073
|
-
}
|
|
1074
|
-
};
|
|
1075
|
-
}
|
|
1076
|
-
let userData = null;
|
|
1077
|
-
if (!userAccount) {
|
|
1078
|
-
if (provider.params.linkAccounts) {
|
|
1079
|
-
const shouldLink = provider.params.linkAccounts.enabler ? await provider.params.linkAccounts.enabler(profile) : true;
|
|
1080
|
-
if (shouldLink) {
|
|
1081
|
-
const { field, key } = provider.params.linkAccounts;
|
|
1082
|
-
const user = await context._db.findOne({
|
|
1083
|
-
model: context.user.modelName,
|
|
1084
|
-
where: [
|
|
1085
|
-
{
|
|
1086
|
-
field,
|
|
1087
|
-
value: profile[key]
|
|
1088
|
-
}
|
|
1089
|
-
]
|
|
1090
|
-
});
|
|
1091
|
-
if (user) {
|
|
1092
|
-
userAccount = await context.adapter.linkAccount(
|
|
1093
|
-
{
|
|
1094
|
-
userId: user.id,
|
|
1095
|
-
providerId: provider.id,
|
|
1096
|
-
accountId: profile.id
|
|
1097
|
-
},
|
|
1098
|
-
context
|
|
1099
|
-
);
|
|
1100
|
-
userData = user;
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
if (!userData && !data) {
|
|
1105
|
-
return {
|
|
1106
|
-
status: 302,
|
|
1107
|
-
headers: {
|
|
1108
|
-
Location: `${callbackURL}?error=user_not_found`
|
|
1109
|
-
}
|
|
1110
|
-
};
|
|
1111
|
-
}
|
|
1112
|
-
if (!userData) {
|
|
1113
|
-
let signUpData = {};
|
|
1114
|
-
for (const key in data) {
|
|
1115
|
-
if (typeof data[key] === "string") {
|
|
1116
|
-
const constructedKey = data[key].split(".");
|
|
1117
|
-
let value = profile;
|
|
1118
|
-
for (const k of constructedKey) {
|
|
1119
|
-
value = value[k];
|
|
1120
|
-
}
|
|
1121
|
-
signUpData[key] = value;
|
|
1122
|
-
} else if ("value" in data[key]) {
|
|
1123
|
-
signUpData[key] = data[key].value;
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
signUpData = parseUser(signUpData, context);
|
|
1127
|
-
const accountData = {
|
|
1128
|
-
providerId: provider.id,
|
|
1129
|
-
accountId: profile.id
|
|
1130
|
-
};
|
|
1131
|
-
for (const key in context.account.additionalFields) {
|
|
1132
|
-
accountData[key] = tokens[context.account.additionalFields[key]];
|
|
1133
|
-
}
|
|
1134
|
-
try {
|
|
1135
|
-
const { user, account } = await context.adapter.createUser(
|
|
1136
|
-
{
|
|
1137
|
-
user: signUpData,
|
|
1138
|
-
account: accountData
|
|
1139
|
-
},
|
|
1140
|
-
context
|
|
1141
|
-
);
|
|
1142
|
-
userAccount = account;
|
|
1143
|
-
userData = user;
|
|
1144
|
-
} catch (e) {
|
|
1145
|
-
return {
|
|
1146
|
-
status: 302,
|
|
1147
|
-
headers: {
|
|
1148
|
-
Location: `${currentURL2}?error=user_already_exist`
|
|
1149
|
-
}
|
|
1150
|
-
};
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
if (!userData) {
|
|
1155
|
-
userData = await context.adapter.findUserById(
|
|
1156
|
-
userAccount?.userId,
|
|
1157
|
-
context
|
|
1158
|
-
);
|
|
1159
|
-
}
|
|
1160
|
-
if (autoCreateSession) {
|
|
1161
|
-
const session2 = await context.adapter.createSession(
|
|
1162
|
-
userData?.id,
|
|
1163
|
-
context
|
|
1164
|
-
);
|
|
1165
|
-
setSessionCookie(context, session2.id);
|
|
2353
|
+
name: process.env.NODE_ENV === "production" ? `${secureCookiePrefix}${cookiePrefix}.${cookieName}` : `${cookiePrefix}.${cookieName}`,
|
|
2354
|
+
options: {
|
|
2355
|
+
secure,
|
|
2356
|
+
sameSite: "lax",
|
|
2357
|
+
path: "/",
|
|
2358
|
+
maxAge: 60 * 15,
|
|
2359
|
+
// 15 minutes in seconds
|
|
2360
|
+
...options2
|
|
1166
2361
|
}
|
|
1167
|
-
return {
|
|
1168
|
-
status: 302,
|
|
1169
|
-
headers: {
|
|
1170
|
-
Location: callbackURL
|
|
1171
|
-
}
|
|
1172
|
-
};
|
|
1173
|
-
}
|
|
1174
|
-
return {
|
|
1175
|
-
status: 200
|
|
1176
2362
|
};
|
|
1177
2363
|
}
|
|
1178
|
-
|
|
1179
|
-
}
|
|
1180
|
-
var callbackHandler = withPlugins(callback, ["csrf"]);
|
|
2364
|
+
return getCookie;
|
|
2365
|
+
}
|
|
1181
2366
|
|
|
1182
|
-
// src/
|
|
1183
|
-
import {
|
|
1184
|
-
var
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
currentURL: z4.string(),
|
|
1188
|
-
callbackURL: z4.string().optional(),
|
|
1189
|
-
autoCreateSession: z4.boolean().optional()
|
|
1190
|
-
});
|
|
1191
|
-
var signUp = async (context) => {
|
|
1192
|
-
const data = signUpSchema.parse(context.request.body);
|
|
1193
|
-
const provider = getProvider(context, data.provider);
|
|
1194
|
-
if (!provider) {
|
|
1195
|
-
throw new ProviderMissing(data.provider);
|
|
1196
|
-
}
|
|
1197
|
-
if (provider?.type === "oauth" || provider?.type === "oidc") {
|
|
1198
|
-
context.request.body.signUp = context.request.body.data;
|
|
1199
|
-
const url = await signInOAuth(context, provider, {
|
|
1200
|
-
autoCreateSession: data.autoCreateSession ?? true,
|
|
1201
|
-
onlySignUp: true
|
|
1202
|
-
});
|
|
1203
|
-
return {
|
|
1204
|
-
status: 200,
|
|
1205
|
-
body: {
|
|
1206
|
-
url,
|
|
1207
|
-
redirect: true
|
|
1208
|
-
}
|
|
1209
|
-
};
|
|
1210
|
-
}
|
|
1211
|
-
if (!provider.signUp) {
|
|
1212
|
-
throw new ProviderError("Sign up method not implemented");
|
|
2367
|
+
// src/utils/logger.ts
|
|
2368
|
+
import { createConsola } from "consola";
|
|
2369
|
+
var consola = createConsola({
|
|
2370
|
+
formatOptions: {
|
|
2371
|
+
date: false
|
|
1213
2372
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
return await plugin.handler(context);
|
|
1241
|
-
}
|
|
1242
|
-
if (providerHandler?.handler) {
|
|
1243
|
-
return await providerHandler.handler.handler(context);
|
|
1244
|
-
}
|
|
1245
|
-
return {
|
|
1246
|
-
status: 404
|
|
1247
|
-
};
|
|
2373
|
+
});
|
|
2374
|
+
var createLogger = (options) => {
|
|
2375
|
+
return {
|
|
2376
|
+
log: (...args) => {
|
|
2377
|
+
!options?.disabled && consola.log("", ...args);
|
|
2378
|
+
},
|
|
2379
|
+
error: (...args) => {
|
|
2380
|
+
!options?.disabled && consola.error("", ...args);
|
|
2381
|
+
},
|
|
2382
|
+
warn: (...args) => {
|
|
2383
|
+
!options?.disabled && consola.warn("", ...args);
|
|
2384
|
+
},
|
|
2385
|
+
info: (...args) => {
|
|
2386
|
+
!options?.disabled && consola.info("", ...args);
|
|
2387
|
+
},
|
|
2388
|
+
debug: (...args) => {
|
|
2389
|
+
!options?.disabled && consola.debug("", ...args);
|
|
2390
|
+
},
|
|
2391
|
+
box: (...args) => {
|
|
2392
|
+
!options?.disabled && consola.box("", ...args);
|
|
2393
|
+
},
|
|
2394
|
+
success: (...args) => {
|
|
2395
|
+
!options?.disabled && consola.success("", ...args);
|
|
2396
|
+
},
|
|
2397
|
+
break: (...args) => {
|
|
2398
|
+
!options?.disabled && console.log("\n");
|
|
1248
2399
|
}
|
|
1249
|
-
}
|
|
2400
|
+
};
|
|
1250
2401
|
};
|
|
2402
|
+
var logger = createLogger();
|
|
1251
2403
|
|
|
1252
|
-
// src/
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
if (request instanceof Request)
|
|
1258
|
-
return await request.json();
|
|
1259
|
-
return new Promise((resolve) => {
|
|
1260
|
-
const bodyParts = [];
|
|
1261
|
-
let body;
|
|
1262
|
-
request.on("data", (chunk) => {
|
|
1263
|
-
bodyParts.push(chunk);
|
|
1264
|
-
}).on("end", () => {
|
|
1265
|
-
body = Buffer.concat(bodyParts).toString();
|
|
1266
|
-
resolve(JSON.parse(body));
|
|
1267
|
-
});
|
|
1268
|
-
});
|
|
1269
|
-
} catch {
|
|
1270
|
-
throw new InvalidRequest();
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1273
|
-
var toRequestHeader = (incomingHeaders) => {
|
|
1274
|
-
if (incomingHeaders instanceof Headers)
|
|
1275
|
-
return incomingHeaders;
|
|
1276
|
-
const headers = new Headers();
|
|
1277
|
-
for (const [key, value] of Object.entries(incomingHeaders)) {
|
|
1278
|
-
if (Array.isArray(value)) {
|
|
1279
|
-
for (const val of value) {
|
|
1280
|
-
headers.append(key, val);
|
|
1281
|
-
}
|
|
1282
|
-
} else if (value) {
|
|
1283
|
-
headers.append(key, value);
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
return headers;
|
|
1287
|
-
};
|
|
1288
|
-
function isValidHttpMethod(method) {
|
|
1289
|
-
if (method?.toUpperCase() !== "POST" && method?.toUpperCase() !== "GET") {
|
|
1290
|
-
return false;
|
|
1291
|
-
}
|
|
1292
|
-
return true;
|
|
1293
|
-
}
|
|
1294
|
-
function parseUrl(request, options) {
|
|
1295
|
-
let requestStringURL = request instanceof IncomingMessage ? `${request.headers.host}${request.url}` : request.url;
|
|
1296
|
-
if (!requestStringURL.startsWith("http")) {
|
|
1297
|
-
if (requestStringURL.startsWith("localhost")) {
|
|
1298
|
-
requestStringURL = `http://${requestStringURL}`;
|
|
1299
|
-
} else {
|
|
1300
|
-
requestStringURL = `https://${requestStringURL}`;
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
const requestURL = new URL(requestStringURL);
|
|
1304
|
-
const baseURL = requestURL.origin;
|
|
1305
|
-
const basePath = options.basePath || "/api/auth";
|
|
1306
|
-
let urlString = `${baseURL}${basePath}`;
|
|
1307
|
-
if (!urlString.startsWith("http")) {
|
|
1308
|
-
urlString = `https://${urlString}`;
|
|
1309
|
-
}
|
|
1310
|
-
const isValidURL = z5.string().url().safeParse(urlString);
|
|
1311
|
-
let action = requestURL.pathname.split(basePath)[1] || "";
|
|
1312
|
-
action = action.replace("/", "");
|
|
1313
|
-
if (isValidURL.error) {
|
|
1314
|
-
throw new InvalidURL();
|
|
1315
|
-
}
|
|
1316
|
-
if (action.startsWith("callback")) {
|
|
1317
|
-
const provider = action.split("/")[1];
|
|
1318
|
-
if (!provider)
|
|
1319
|
-
throw new InvalidURL("Provider is missing in the URL.");
|
|
1320
|
-
action = "callback";
|
|
1321
|
-
requestURL.searchParams.set("provider", provider);
|
|
1322
|
-
}
|
|
1323
|
-
const url = new URL(urlString);
|
|
1324
|
-
requestURL.searchParams.forEach((value, key) => {
|
|
1325
|
-
url.searchParams.set(key, value);
|
|
1326
|
-
});
|
|
2404
|
+
// src/init.ts
|
|
2405
|
+
var init = (options) => {
|
|
2406
|
+
const adapter = getAdapter(options);
|
|
2407
|
+
const db = createKyselyAdapter(options);
|
|
2408
|
+
const { baseURL, withPath: withPath2 } = getBaseURL(options.baseURL, options.basePath);
|
|
1327
2409
|
return {
|
|
1328
|
-
|
|
1329
|
-
|
|
2410
|
+
options: {
|
|
2411
|
+
...options,
|
|
2412
|
+
baseURL,
|
|
2413
|
+
basePath: options.basePath || "/api/auth"
|
|
2414
|
+
},
|
|
2415
|
+
baseURL: withPath2,
|
|
2416
|
+
secret: options.secret || process.env.BETTER_AUTH_SECRET || process.env.AUTH_SECRET || "better-auth-secret-123456789",
|
|
2417
|
+
authCookies: getCookies(options),
|
|
2418
|
+
logger: createLogger({
|
|
2419
|
+
disabled: options.disableLog
|
|
2420
|
+
}),
|
|
2421
|
+
db,
|
|
2422
|
+
adapter,
|
|
2423
|
+
internalAdapter: createInternalAdapter(adapter, options),
|
|
2424
|
+
createAuthCookie: createCookieGetter(options)
|
|
1330
2425
|
};
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
// src/utils/secret.ts
|
|
1334
|
-
var DEFAULT_SECRET = "better-auth-secret-key-123456789";
|
|
1335
|
-
var getSecret = (secret) => {
|
|
1336
|
-
secret = secret || process.env.BETTER_AUTH_SECRET || process.env.AUTH_SECRET;
|
|
1337
|
-
if (process.env.NODE_ENV === "production" && !secret) {
|
|
1338
|
-
throw new MissingSecret();
|
|
1339
|
-
}
|
|
1340
|
-
return secret || DEFAULT_SECRET;
|
|
1341
2426
|
};
|
|
1342
2427
|
|
|
1343
2428
|
// src/auth.ts
|
|
1344
2429
|
var betterAuth = (options) => {
|
|
1345
|
-
const
|
|
1346
|
-
const
|
|
1347
|
-
/**
|
|
1348
|
-
* The handler for the better auth routes.
|
|
1349
|
-
*/
|
|
1350
|
-
handler: async (request, opts) => {
|
|
1351
|
-
const context = await toContext(options, request, opts);
|
|
1352
|
-
if (!isValidHttpMethod(request.method)) {
|
|
1353
|
-
return toResponse({ status: 200 }, context, opts);
|
|
1354
|
-
}
|
|
1355
|
-
const response = await router(context);
|
|
1356
|
-
return toResponse(response, context, opts);
|
|
1357
|
-
},
|
|
1358
|
-
caller: {
|
|
1359
|
-
...defaultActions,
|
|
1360
|
-
...options.plugins?.map((plugin) => plugin.getActions?.(options))
|
|
1361
|
-
},
|
|
1362
|
-
options
|
|
1363
|
-
};
|
|
1364
|
-
return auth;
|
|
1365
|
-
};
|
|
1366
|
-
var toContext = async (options, request, handlerOptions) => {
|
|
1367
|
-
const basePath = options.basePath || "/api/auth";
|
|
1368
|
-
const headers = toRequestHeader(request.headers);
|
|
1369
|
-
const { url, action } = parseUrl(request, options);
|
|
1370
|
-
const body = request.method?.toUpperCase() === "POST" ? await getBody(request) : null;
|
|
2430
|
+
const authContext = init(options);
|
|
2431
|
+
const { handler, endpoints } = router(authContext);
|
|
1371
2432
|
return {
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
method: request.method,
|
|
1376
|
-
url,
|
|
1377
|
-
query: Object.fromEntries(url.searchParams),
|
|
1378
|
-
action,
|
|
1379
|
-
body,
|
|
1380
|
-
headers,
|
|
1381
|
-
cookies: handlerOptions?.cookieManager || cookieManager(headers)
|
|
1382
|
-
},
|
|
1383
|
-
_db: options.adapter,
|
|
1384
|
-
providers: options.providers,
|
|
1385
|
-
secret: getSecret(options.secret),
|
|
1386
|
-
adapter: createInternalAdapter(options.adapter),
|
|
1387
|
-
plugins: getPlugins(options),
|
|
1388
|
-
cookies: getCookies(options),
|
|
1389
|
-
disableCSRF: options.advanced?.skipCSRFCheck || false,
|
|
1390
|
-
session: {
|
|
1391
|
-
modelName: options.session?.modelName || "session",
|
|
1392
|
-
updateAge: options.session?.updateAge === void 0 ? timeSpan("1d") : options.session.updateAge,
|
|
1393
|
-
expiresIn: options.session?.expiresIn || timeSpan("1w"),
|
|
1394
|
-
additionalFields: options.session?.additionalFields ? toInternalFields(options.session.additionalFields) : {},
|
|
1395
|
-
selectFields: getSelectFields(
|
|
1396
|
-
options.session?.additionalFields || {},
|
|
1397
|
-
"session"
|
|
1398
|
-
)
|
|
1399
|
-
},
|
|
1400
|
-
user: {
|
|
1401
|
-
modelName: options.user?.modelName || "user",
|
|
1402
|
-
fields: options.user?.fields ? toInternalFields(options.user.fields) : {},
|
|
1403
|
-
selectFields: getSelectFields(options.user?.fields || {}, "user")
|
|
1404
|
-
},
|
|
1405
|
-
account: {
|
|
1406
|
-
modelName: options.account?.modelName || "account",
|
|
1407
|
-
additionalFields: options.account?.additionalFields || {},
|
|
1408
|
-
selectFields: [
|
|
1409
|
-
...Object.keys(options.account?.additionalFields || {}),
|
|
1410
|
-
"userId",
|
|
1411
|
-
"providerId",
|
|
1412
|
-
"accountId"
|
|
1413
|
-
]
|
|
1414
|
-
},
|
|
1415
|
-
sessionAdapter: options.sessionAdapter
|
|
2433
|
+
handler,
|
|
2434
|
+
api: endpoints,
|
|
2435
|
+
options
|
|
1416
2436
|
};
|
|
1417
2437
|
};
|
|
1418
|
-
var toResponse = (res, context, handlerOptions) => {
|
|
1419
|
-
if (handlerOptions?.toResponse) {
|
|
1420
|
-
return handlerOptions.toResponse(res, context);
|
|
1421
|
-
}
|
|
1422
|
-
context.request.headers.set("content-type", "application/json");
|
|
1423
|
-
const response = new Response(res.body ? JSON.stringify(res.body) : null, {
|
|
1424
|
-
headers: {
|
|
1425
|
-
...context.request.headers,
|
|
1426
|
-
"Set-Cookie": context.request.headers.get("Set-Cookie") ?? "",
|
|
1427
|
-
...res.headers
|
|
1428
|
-
},
|
|
1429
|
-
status: res.status,
|
|
1430
|
-
statusText: res.statusText
|
|
1431
|
-
});
|
|
1432
|
-
return response;
|
|
1433
|
-
};
|
|
1434
2438
|
export {
|
|
1435
2439
|
betterAuth,
|
|
1436
|
-
|
|
1437
|
-
|
|
2440
|
+
createFieldAttribute,
|
|
2441
|
+
createInternalAdapter
|
|
1438
2442
|
};
|
|
1439
2443
|
//# sourceMappingURL=index.js.map
|