@cedarjs/auth-dbauth-setup 7.0.1-next.466 → 7.1.0-rc.32
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/setup.d.ts +0 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +0 -5
- package/dist/setupData.d.ts +1 -1
- package/dist/setupData.d.ts.map +1 -1
- package/dist/setupData.js +1 -3
- package/dist/setupHandler.d.ts +1 -1
- package/dist/setupHandler.d.ts.map +1 -1
- package/dist/setupHandler.js +4 -50
- package/dist/shared.js +1 -1
- package/dist/templates/api/functions/auth.ts.template +0 -37
- package/dist/templates/api/functions/auth.webAuthn.ts.template +0 -37
- package/dist/webAuthn.setupData.d.ts +1 -1
- package/dist/webAuthn.setupData.d.ts.map +1 -1
- package/dist/webAuthn.setupData.js +3 -4
- package/package.json +6 -6
- package/dist/oauth.setupData.d.ts +0 -57
- package/dist/oauth.setupData.d.ts.map +0 -1
- package/dist/oauth.setupData.js +0 -336
- package/dist/templates/api/functions/auth.oauth.ts.template +0 -364
- package/dist/templates/api/functions/auth.webAuthn.oauth.ts.template +0 -382
package/dist/oauth.setupData.js
DELETED
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { colors } from "@cedarjs/cli-helpers/colors";
|
|
4
|
-
import { getPaths } from "@cedarjs/cli-helpers/paths";
|
|
5
|
-
import { isTypeScriptProject } from "@cedarjs/cli-helpers/project";
|
|
6
|
-
import { addModels, functionsPath, hasModel, libPath } from "./shared.js";
|
|
7
|
-
const KNOWN_OAUTH_PROVIDERS = ["google", "github"];
|
|
8
|
-
function isKnownOAuthProvider(value) {
|
|
9
|
-
return KNOWN_OAUTH_PROVIDERS.includes(value);
|
|
10
|
-
}
|
|
11
|
-
function parseOAuthProviders(oauth) {
|
|
12
|
-
if (oauth === null || oauth === void 0) {
|
|
13
|
-
return [];
|
|
14
|
-
}
|
|
15
|
-
const requested = oauth.split(",").map((provider) => provider.trim().toLowerCase()).filter(Boolean);
|
|
16
|
-
const uniqueRequested = [...new Set(requested)];
|
|
17
|
-
if (uniqueRequested.length === 0) {
|
|
18
|
-
throw new Error(
|
|
19
|
-
"--oauth requires at least one provider, e.g. --oauth google,github"
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
const unknownProviders = uniqueRequested.filter(
|
|
23
|
-
(provider) => !isKnownOAuthProvider(provider)
|
|
24
|
-
);
|
|
25
|
-
if (unknownProviders.length > 0) {
|
|
26
|
-
throw new Error(
|
|
27
|
-
`Unknown OAuth provider(s): ${unknownProviders.join(", ")}. Cedar ships built-in support for ${KNOWN_OAUTH_PROVIDERS.join(
|
|
28
|
-
", "
|
|
29
|
-
)}. For any other provider, implement the \`OAuthStrategy\` interface from \`@cedarjs/auth-dbauth-oauth\` -- see https://cedarjs.com/docs/auth/dbauth#oauth-custom-strategies`
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
return uniqueRequested;
|
|
33
|
-
}
|
|
34
|
-
const apiPackages = ["oauth4webapi@^3"];
|
|
35
|
-
const OAUTH_MODEL = `
|
|
36
|
-
model OAuth {
|
|
37
|
-
id String @id @default(uuid())
|
|
38
|
-
provider String
|
|
39
|
-
providerUserId String
|
|
40
|
-
providerUsername String?
|
|
41
|
-
providerEmail String?
|
|
42
|
-
userId String
|
|
43
|
-
user User @relation(fields: [userId], references: [id])
|
|
44
|
-
createdAt DateTime @default(now())
|
|
45
|
-
updatedAt DateTime @updatedAt
|
|
46
|
-
|
|
47
|
-
@@unique([provider, providerUserId])
|
|
48
|
-
@@unique([userId, provider])
|
|
49
|
-
}
|
|
50
|
-
`;
|
|
51
|
-
const createUserModelTask = {
|
|
52
|
-
title: "Creating model `User`...",
|
|
53
|
-
task: async (ctx) => {
|
|
54
|
-
const hasUserModel = await hasModel("User");
|
|
55
|
-
if (hasUserModel && !ctx.force) {
|
|
56
|
-
throw new Error("User model already exists");
|
|
57
|
-
}
|
|
58
|
-
addModels(`
|
|
59
|
-
model User {
|
|
60
|
-
id String @id @default(uuid())
|
|
61
|
-
email String @unique
|
|
62
|
-
hashedPassword String?
|
|
63
|
-
salt String?
|
|
64
|
-
resetToken String?
|
|
65
|
-
resetTokenExpiresAt DateTime?
|
|
66
|
-
oauthIdentities OAuth[]
|
|
67
|
-
createdAt DateTime @default(now())
|
|
68
|
-
updatedAt DateTime @updatedAt
|
|
69
|
-
}
|
|
70
|
-
${OAUTH_MODEL}`);
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
const webAuthnCreateUserModelTask = {
|
|
74
|
-
title: "Creating model `User`...",
|
|
75
|
-
task: async (ctx) => {
|
|
76
|
-
const hasUserModel = await hasModel("User");
|
|
77
|
-
if (hasUserModel && !ctx.force) {
|
|
78
|
-
throw new Error("User model already exists");
|
|
79
|
-
}
|
|
80
|
-
addModels(`
|
|
81
|
-
model User {
|
|
82
|
-
id String @id @default(uuid())
|
|
83
|
-
email String @unique
|
|
84
|
-
hashedPassword String?
|
|
85
|
-
salt String?
|
|
86
|
-
resetToken String?
|
|
87
|
-
resetTokenExpiresAt DateTime?
|
|
88
|
-
webAuthnChallenge String? @unique
|
|
89
|
-
credentials UserCredential[]
|
|
90
|
-
oauthIdentities OAuth[]
|
|
91
|
-
createdAt DateTime @default(now())
|
|
92
|
-
updatedAt DateTime @updatedAt
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
model UserCredential {
|
|
96
|
-
id String @id
|
|
97
|
-
userId String
|
|
98
|
-
user User @relation(fields: [userId], references: [id])
|
|
99
|
-
publicKey Bytes
|
|
100
|
-
transports String?
|
|
101
|
-
counter BigInt
|
|
102
|
-
}
|
|
103
|
-
${OAUTH_MODEL}`);
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
const OAUTH_ENV_VAR_NAMES = {
|
|
107
|
-
google: ["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET"],
|
|
108
|
-
github: ["GITHUB_CLIENT_ID", "GITHUB_CLIENT_SECRET"]
|
|
109
|
-
};
|
|
110
|
-
function envVarNotes(providers) {
|
|
111
|
-
return [
|
|
112
|
-
"",
|
|
113
|
-
"Add the client id and secret for each configured OAuth provider to your .env file (get these from each provider\u2019s developer console):",
|
|
114
|
-
"",
|
|
115
|
-
...providers.flatMap((provider) => [
|
|
116
|
-
` # ${provider}`,
|
|
117
|
-
...OAUTH_ENV_VAR_NAMES[provider].map((name) => ` ${name}=`)
|
|
118
|
-
])
|
|
119
|
-
];
|
|
120
|
-
}
|
|
121
|
-
function schemaNotes() {
|
|
122
|
-
return [
|
|
123
|
-
"",
|
|
124
|
-
"You will also need to add an `OAuth` model to your Prisma schema to",
|
|
125
|
-
"keep track of linked provider accounts:",
|
|
126
|
-
"",
|
|
127
|
-
" model OAuth {",
|
|
128
|
-
" id String @id @default(uuid())",
|
|
129
|
-
" provider String",
|
|
130
|
-
" providerUserId String",
|
|
131
|
-
" providerUsername String?",
|
|
132
|
-
" providerEmail String?",
|
|
133
|
-
" userId String",
|
|
134
|
-
" user User @relation(fields: [userId], references: [id])",
|
|
135
|
-
" createdAt DateTime @default(now())",
|
|
136
|
-
" updatedAt DateTime @updatedAt",
|
|
137
|
-
"",
|
|
138
|
-
" @@unique([provider, providerUserId])",
|
|
139
|
-
" @@unique([userId, provider])",
|
|
140
|
-
" }",
|
|
141
|
-
"",
|
|
142
|
-
"Since users can now sign up with just an OAuth provider (no password),",
|
|
143
|
-
"`hashedPassword` and `salt` on your `User` model must be optional (or",
|
|
144
|
-
"have a default value):",
|
|
145
|
-
"",
|
|
146
|
-
" hashedPassword String?",
|
|
147
|
-
" salt String?",
|
|
148
|
-
"",
|
|
149
|
-
"Don't forget to add the inverse relation to `User`:",
|
|
150
|
-
"",
|
|
151
|
-
" oauthIdentities OAuth[]"
|
|
152
|
-
];
|
|
153
|
-
}
|
|
154
|
-
function notes() {
|
|
155
|
-
return [
|
|
156
|
-
`${colors.warning("Done! But you have a little more work to do:")}
|
|
157
|
-
`,
|
|
158
|
-
"You will need to add a couple of fields to your User table in order",
|
|
159
|
-
"to store a hashed password and salt:",
|
|
160
|
-
"",
|
|
161
|
-
" model User {",
|
|
162
|
-
" id String @id @default(uuid())",
|
|
163
|
-
" email String @unique",
|
|
164
|
-
" hashedPassword String? // <\u2500\u2510",
|
|
165
|
-
" salt String? // <\u2500\u2534\u2500 add these lines",
|
|
166
|
-
" resetToken String? // <\u2500\u2524",
|
|
167
|
-
" resetTokenExpiresAt DateTime? // <\u2500\u2518",
|
|
168
|
-
" }",
|
|
169
|
-
...schemaNotes(),
|
|
170
|
-
"",
|
|
171
|
-
"If you expose any of your user data via GraphQL be sure to exclude",
|
|
172
|
-
"`hashedPassword` and `salt` (or whatever you named them) from the",
|
|
173
|
-
"SDL file that defines the fields for your user.",
|
|
174
|
-
"",
|
|
175
|
-
"You'll need to let Cedar know what fields you're using for your users'",
|
|
176
|
-
"`id` and `username` fields. In this case we're using `id` and `email`,",
|
|
177
|
-
"so update those in the `authFields` config in",
|
|
178
|
-
`\`${functionsPath}/auth.js\`. This is also the place to tell Cedar if`,
|
|
179
|
-
"you used a different name for the `hashedPassword`, `salt`,",
|
|
180
|
-
"`resetToken` or `resetTokenExpiresAt`, fields:`",
|
|
181
|
-
"",
|
|
182
|
-
" authFields: {",
|
|
183
|
-
" id: 'id',",
|
|
184
|
-
" username: 'email',",
|
|
185
|
-
" hashedPassword: 'hashedPassword',",
|
|
186
|
-
" salt: 'salt',",
|
|
187
|
-
" resetToken: 'resetToken',",
|
|
188
|
-
" resetTokenExpiresAt: 'resetTokenExpiresAt',",
|
|
189
|
-
" },",
|
|
190
|
-
"",
|
|
191
|
-
"To get the actual user that's logged in, take a look at `getCurrentUser()`",
|
|
192
|
-
`in \`${libPath}/auth.js\`. We default it to something simple, but you may`,
|
|
193
|
-
"use different names for your model or unique ID fields, in which case you",
|
|
194
|
-
"need to update those calls (instructions are in the comment above the code).",
|
|
195
|
-
"",
|
|
196
|
-
"Finally, we created a SESSION_SECRET environment variable for you in",
|
|
197
|
-
`${path.join(getPaths().base, ".env")}. This value should NOT be checked`,
|
|
198
|
-
"into version control and should be unique for each environment you",
|
|
199
|
-
"deploy to. If you ever need to log everyone out of your app at once",
|
|
200
|
-
"change this secret to a new value and deploy. To create a new secret, run:",
|
|
201
|
-
"",
|
|
202
|
-
" yarn cedar generate secret",
|
|
203
|
-
""
|
|
204
|
-
];
|
|
205
|
-
}
|
|
206
|
-
function webAuthnOauthNotes() {
|
|
207
|
-
return [
|
|
208
|
-
`${colors.warning("Done! But you have a little more work to do:")}
|
|
209
|
-
`,
|
|
210
|
-
"You will need to add a couple of fields to your User table in order",
|
|
211
|
-
"to store a hashed password, salt, reset token, and to connect it to",
|
|
212
|
-
"a new UserCredential model to keep track of any devices used with",
|
|
213
|
-
"WebAuthn authentication:",
|
|
214
|
-
"",
|
|
215
|
-
" model User {",
|
|
216
|
-
" id String @id @default(uuid())",
|
|
217
|
-
" email String @unique",
|
|
218
|
-
" hashedPassword String?",
|
|
219
|
-
" salt String?",
|
|
220
|
-
" resetToken String?",
|
|
221
|
-
" resetTokenExpiresAt DateTime?",
|
|
222
|
-
" webAuthnChallenge String? @unique",
|
|
223
|
-
" credentials UserCredential[]",
|
|
224
|
-
" }",
|
|
225
|
-
"",
|
|
226
|
-
" model UserCredential {",
|
|
227
|
-
" id String @id",
|
|
228
|
-
" userId String",
|
|
229
|
-
" user User @relation(fields: [userId], references: [id])",
|
|
230
|
-
" publicKey Bytes",
|
|
231
|
-
" transports String?",
|
|
232
|
-
" counter BigInt",
|
|
233
|
-
" }",
|
|
234
|
-
...schemaNotes(),
|
|
235
|
-
"",
|
|
236
|
-
"If you expose any of your user data via GraphQL be sure to exclude",
|
|
237
|
-
"`hashedPassword` and `salt` (or whatever you named them) from the",
|
|
238
|
-
"SDL file that defines the fields for your user.",
|
|
239
|
-
"",
|
|
240
|
-
"You'll need to let Cedar know what fields you're using for your users'",
|
|
241
|
-
"`id` and `username` fields. In this case we're using `id` and `email`,",
|
|
242
|
-
"so update those in the `authFields` config in",
|
|
243
|
-
`\`${functionsPath}/auth.js\`. This is also the place to tell Cedar if`,
|
|
244
|
-
"you used a different name for the `hashedPassword`, `salt`,",
|
|
245
|
-
"`resetToken` or `resetTokenExpiresAt`, fields:`",
|
|
246
|
-
"",
|
|
247
|
-
" authFields: {",
|
|
248
|
-
" id: 'id',",
|
|
249
|
-
" username: 'email',",
|
|
250
|
-
" hashedPassword: 'hashedPassword',",
|
|
251
|
-
" salt: 'salt',",
|
|
252
|
-
" resetToken: 'resetToken',",
|
|
253
|
-
" resetTokenExpiresAt: 'resetTokenExpiresAt',",
|
|
254
|
-
" challenge: 'webAuthnChallenge'",
|
|
255
|
-
" },",
|
|
256
|
-
"",
|
|
257
|
-
"To get the actual user that's logged in, take a look at `getCurrentUser()`",
|
|
258
|
-
`in \`${libPath}/auth.js\`. We default it to something simple, but you may`,
|
|
259
|
-
"use different names for your model or unique ID fields, in which case you",
|
|
260
|
-
"need to update those calls (instructions are in the comment above the code).",
|
|
261
|
-
"",
|
|
262
|
-
"Finally, we created a SESSION_SECRET environment variable for you in",
|
|
263
|
-
`${path.join(getPaths().base, ".env")}. This value should NOT be checked`,
|
|
264
|
-
"into version control and should be unique for each environment you",
|
|
265
|
-
"deploy to. If you ever need to log everyone out of your app at once",
|
|
266
|
-
"change this secret to a new value and deploy. To create a new secret, run:",
|
|
267
|
-
"",
|
|
268
|
-
" yarn cedar generate secret",
|
|
269
|
-
""
|
|
270
|
-
];
|
|
271
|
-
}
|
|
272
|
-
const noteGenerate = [
|
|
273
|
-
"",
|
|
274
|
-
"Need simple Login, Signup and Forgot Password pages? We've got a generator",
|
|
275
|
-
"for those as well:",
|
|
276
|
-
"",
|
|
277
|
-
" yarn cedar generate dbAuth"
|
|
278
|
-
];
|
|
279
|
-
function pruneOAuthProviders(content, providers) {
|
|
280
|
-
let result = content;
|
|
281
|
-
for (const provider of KNOWN_OAUTH_PROVIDERS) {
|
|
282
|
-
const startMarker = `// @oauth-provider:${provider}`;
|
|
283
|
-
const endMarker = `// @oauth-provider:${provider}:end`;
|
|
284
|
-
const blockPattern = new RegExp(
|
|
285
|
-
`[ \\t]*${escapeRegExp(startMarker)}\\n([\\s\\S]*?)[ \\t]*${escapeRegExp(
|
|
286
|
-
endMarker
|
|
287
|
-
)}\\n`
|
|
288
|
-
);
|
|
289
|
-
if (providers.includes(provider)) {
|
|
290
|
-
result = result.replace(blockPattern, "$1");
|
|
291
|
-
} else {
|
|
292
|
-
result = result.replace(blockPattern, "");
|
|
293
|
-
result = result.replace(
|
|
294
|
-
new RegExp(`^[ \\t]*${provider}Provider,\\n`, "m"),
|
|
295
|
-
""
|
|
296
|
-
);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
return result;
|
|
300
|
-
}
|
|
301
|
-
function escapeRegExp(str) {
|
|
302
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
303
|
-
}
|
|
304
|
-
function configureOAuthProvidersTask(providers) {
|
|
305
|
-
return {
|
|
306
|
-
title: "Configuring selected OAuth providers...",
|
|
307
|
-
task: () => {
|
|
308
|
-
const ext = isTypeScriptProject() ? "ts" : "js";
|
|
309
|
-
const authFunctionPath = path.join(
|
|
310
|
-
getPaths().api.functions,
|
|
311
|
-
`auth.${ext}`
|
|
312
|
-
);
|
|
313
|
-
if (!fs.existsSync(authFunctionPath)) {
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
const content = fs.readFileSync(authFunctionPath, "utf-8");
|
|
317
|
-
const prunedContent = pruneOAuthProviders(content, providers);
|
|
318
|
-
if (prunedContent !== content) {
|
|
319
|
-
fs.writeFileSync(authFunctionPath, prunedContent);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
export {
|
|
325
|
-
KNOWN_OAUTH_PROVIDERS,
|
|
326
|
-
apiPackages,
|
|
327
|
-
configureOAuthProvidersTask,
|
|
328
|
-
createUserModelTask,
|
|
329
|
-
envVarNotes,
|
|
330
|
-
noteGenerate,
|
|
331
|
-
notes,
|
|
332
|
-
parseOAuthProviders,
|
|
333
|
-
pruneOAuthProviders,
|
|
334
|
-
webAuthnCreateUserModelTask,
|
|
335
|
-
webAuthnOauthNotes
|
|
336
|
-
};
|
|
@@ -1,364 +0,0 @@
|
|
|
1
|
-
import type { APIGatewayProxyEvent, Context } from 'aws-lambda'
|
|
2
|
-
|
|
3
|
-
import { DbAuthHandler } from '@cedarjs/auth-dbauth-api'
|
|
4
|
-
import type { DbAuthHandlerOptions, UserType } from '@cedarjs/auth-dbauth-api'
|
|
5
|
-
import {
|
|
6
|
-
OAuthHandler,
|
|
7
|
-
googleProvider,
|
|
8
|
-
githubProvider,
|
|
9
|
-
} from '@cedarjs/auth-dbauth-oauth'
|
|
10
|
-
|
|
11
|
-
import { cookieName } from 'src/lib/auth'
|
|
12
|
-
import { db } from 'src/lib/db'
|
|
13
|
-
|
|
14
|
-
// Any request whose path falls under this prefix is routed to the OAuth
|
|
15
|
-
// handler instead of the regular username/password dbAuth handler. This
|
|
16
|
-
// must match the callback URLs registered with each provider below (e.g.
|
|
17
|
-
// `${apiUrl}/auth/oauth/google/callback`).
|
|
18
|
-
const OAUTH_BASE_PATH = '/auth/oauth'
|
|
19
|
-
|
|
20
|
-
// The base URL of your deployed api side. Used to build the OAuth callback
|
|
21
|
-
// URLs registered with each provider's developer console. Update the
|
|
22
|
-
// production value once you know your deployed URL.
|
|
23
|
-
const apiUrl =
|
|
24
|
-
process.env.NODE_ENV === 'development'
|
|
25
|
-
? 'http://localhost:8911'
|
|
26
|
-
: 'https://server.com'
|
|
27
|
-
|
|
28
|
-
const cookieAttributes = {
|
|
29
|
-
HttpOnly: true,
|
|
30
|
-
Path: '/',
|
|
31
|
-
SameSite: 'Lax' as const,
|
|
32
|
-
Secure: process.env.NODE_ENV !== 'development',
|
|
33
|
-
|
|
34
|
-
// If you need to allow other domains (besides the api side) access to
|
|
35
|
-
// the dbAuth session cookie:
|
|
36
|
-
// Domain: 'example.com',
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export const handler = async (
|
|
40
|
-
event: APIGatewayProxyEvent,
|
|
41
|
-
context: Context
|
|
42
|
-
) => {
|
|
43
|
-
if (event.path?.includes(OAUTH_BASE_PATH)) {
|
|
44
|
-
const oauthHandler = new OAuthHandler(event, context, {
|
|
45
|
-
// Provide prisma db client
|
|
46
|
-
db: db,
|
|
47
|
-
|
|
48
|
-
// The name of the property you'd call on `db` to access your user
|
|
49
|
-
// table. i.e. if your Prisma model is named `User` this value would
|
|
50
|
-
// be `user`, as in `db.user`
|
|
51
|
-
authModelAccessor: 'user',
|
|
52
|
-
|
|
53
|
-
// The name of the property you'd call on `db` to access your OAuth
|
|
54
|
-
// identity table. i.e. if your Prisma model is named `OAuth` this
|
|
55
|
-
// value would be `oAuth`, as in `db.oAuth`
|
|
56
|
-
oauthModelAccessor: 'oAuth',
|
|
57
|
-
|
|
58
|
-
// A map of what dbAuth calls a field to what your database calls it,
|
|
59
|
-
// matching the same fields used by `DbAuthHandler` below
|
|
60
|
-
authFields: {
|
|
61
|
-
id: 'id',
|
|
62
|
-
username: 'email',
|
|
63
|
-
hashedPassword: 'hashedPassword',
|
|
64
|
-
},
|
|
65
|
-
|
|
66
|
-
basePath: OAUTH_BASE_PATH,
|
|
67
|
-
|
|
68
|
-
// The configured OAuth providers, keyed by the path segment used in
|
|
69
|
-
// `/auth/oauth/{key}/...`. Get client id/secret values from each
|
|
70
|
-
// provider's developer console and set them in your `.env` file.
|
|
71
|
-
providers: {
|
|
72
|
-
// @oauth-provider:google
|
|
73
|
-
google: googleProvider({
|
|
74
|
-
clientId: process.env.GOOGLE_CLIENT_ID as string,
|
|
75
|
-
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
|
|
76
|
-
redirectUri: `${apiUrl}/auth/oauth/google/callback`,
|
|
77
|
-
}),
|
|
78
|
-
// @oauth-provider:google:end
|
|
79
|
-
// @oauth-provider:github
|
|
80
|
-
github: githubProvider({
|
|
81
|
-
clientId: process.env.GITHUB_CLIENT_ID as string,
|
|
82
|
-
clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
|
|
83
|
-
redirectUri: `${apiUrl}/auth/oauth/github/callback`,
|
|
84
|
-
}),
|
|
85
|
-
// @oauth-provider:github:end
|
|
86
|
-
},
|
|
87
|
-
|
|
88
|
-
// Where to send the browser after each flow completes
|
|
89
|
-
redirects: {
|
|
90
|
-
afterLogin: '/',
|
|
91
|
-
error: '/login',
|
|
92
|
-
},
|
|
93
|
-
|
|
94
|
-
signup: {
|
|
95
|
-
// handler() is invoked the first time a user signs in with a
|
|
96
|
-
// configured OAuth provider and no account is linked to that
|
|
97
|
-
// provider identity yet. `profile` is the canonical profile the
|
|
98
|
-
// provider returned (`providerUserId`, `email`, `username`, etc).
|
|
99
|
-
//
|
|
100
|
-
// At a minimum you need to create and return a user row. There's no
|
|
101
|
-
// password to store here since this account only ever signs in
|
|
102
|
-
// through the provider -- if your `User` model requires
|
|
103
|
-
// `hashedPassword`/`salt`, make sure those fields are optional (or
|
|
104
|
-
// have a default) so provider-only accounts can be created.
|
|
105
|
-
handler: ({ profile }) => {
|
|
106
|
-
// Some providers (e.g. GitHub, when the account keeps its email
|
|
107
|
-
// private) don't return an email at all. Customize this handler
|
|
108
|
-
// for any such provider you configure -- ask for an email on a
|
|
109
|
-
// follow-up screen, or derive a placeholder from a field you know
|
|
110
|
-
// is always present. Throwing here surfaces to the user as a
|
|
111
|
-
// `provider_error` redirect instead of silently creating an
|
|
112
|
-
// account with a fabricated address.
|
|
113
|
-
if (!profile.email) {
|
|
114
|
-
throw new Error(
|
|
115
|
-
`OAuth signup handler needs an email, but ${profile.providerUserId} didn't return one. Customize this handler to handle providers that don't supply an email.`,
|
|
116
|
-
)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return db.user.create({
|
|
120
|
-
data: {
|
|
121
|
-
email: profile.email,
|
|
122
|
-
},
|
|
123
|
-
})
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
|
|
127
|
-
// How long a user will remain logged in, in seconds
|
|
128
|
-
sessionExpires: 60 * 60 * 24 * 365 * 10,
|
|
129
|
-
|
|
130
|
-
// Specifies attributes on the cookie that dbAuth sets in order to
|
|
131
|
-
// remember who is logged in. See
|
|
132
|
-
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies
|
|
133
|
-
cookie: {
|
|
134
|
-
attributes: cookieAttributes,
|
|
135
|
-
name: cookieName,
|
|
136
|
-
},
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
return await oauthHandler.invoke()
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const forgotPasswordOptions: DbAuthHandlerOptions['forgotPassword'] = {
|
|
143
|
-
// handler() is invoked after verifying that a user was found with the given
|
|
144
|
-
// username. This is where you can send the user an email with a link to
|
|
145
|
-
// reset their password.
|
|
146
|
-
//
|
|
147
|
-
// Whatever is returned from this function will be returned from
|
|
148
|
-
// the `forgotPassword()` function that is destructured from `useAuth()`.
|
|
149
|
-
// You could use this return value to, for example, show the email
|
|
150
|
-
// address in a toast message so the user will know it worked and where
|
|
151
|
-
// to look for the email.
|
|
152
|
-
//
|
|
153
|
-
// Note that this return value is sent to the client in *plain text*
|
|
154
|
-
// so don't include anything you wouldn't want prying eyes to see. The
|
|
155
|
-
// `user` here has been sanitized to only include the fields listed in
|
|
156
|
-
// `allowedUserFields` so it should be safe to return as-is.
|
|
157
|
-
handler: (user, _resetToken) => {
|
|
158
|
-
// TODO: Send an email/message to the user
|
|
159
|
-
// The message should include a link to reset their password with the
|
|
160
|
-
// `resetToken`. The URL should look something like:
|
|
161
|
-
// `http://localhost:8910/reset-password?resetToken=${resetToken}`
|
|
162
|
-
// When you implement this, change `_resetToken` to `resetToken` in the
|
|
163
|
-
// function arguments above.
|
|
164
|
-
|
|
165
|
-
return user
|
|
166
|
-
},
|
|
167
|
-
|
|
168
|
-
// How long the resetToken is valid for, in seconds (default is 24 hours)
|
|
169
|
-
expires: 60 * 60 * 24,
|
|
170
|
-
|
|
171
|
-
errors: {
|
|
172
|
-
// for security reasons you may want to be vague here rather than expose
|
|
173
|
-
// the fact that the email address wasn't found (prevents fishing for
|
|
174
|
-
// valid email addresses)
|
|
175
|
-
usernameNotFound: 'Username not found',
|
|
176
|
-
// if the user somehow gets around client validation
|
|
177
|
-
usernameRequired: 'Username is required',
|
|
178
|
-
},
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const loginOptions: DbAuthHandlerOptions['login'] = {
|
|
182
|
-
// handler() is called after finding the user that matches the
|
|
183
|
-
// username/password provided at login, but before actually considering them
|
|
184
|
-
// logged in. The `user` argument will be the user in the database that
|
|
185
|
-
// matched the username/password.
|
|
186
|
-
//
|
|
187
|
-
// If you want to allow this user to log in simply return the user.
|
|
188
|
-
//
|
|
189
|
-
// If you want to prevent someone logging in for another reason (maybe they
|
|
190
|
-
// didn't validate their email yet), throw an error and it will be returned
|
|
191
|
-
// by the `logIn()` function from `useAuth()` in the form of:
|
|
192
|
-
// `{ message: 'Error message' }`
|
|
193
|
-
handler: (user) => {
|
|
194
|
-
return user
|
|
195
|
-
},
|
|
196
|
-
|
|
197
|
-
errors: {
|
|
198
|
-
usernameOrPasswordMissing: 'Both username and password are required',
|
|
199
|
-
usernameNotFound: 'Username ${username} not found',
|
|
200
|
-
// For security reasons you may want to make this the same as the
|
|
201
|
-
// usernameNotFound error so that a malicious user can't use the error
|
|
202
|
-
// to narrow down if it's the username or password that's incorrect
|
|
203
|
-
incorrectPassword: 'Incorrect password for ${username}',
|
|
204
|
-
},
|
|
205
|
-
|
|
206
|
-
// How long a user will remain logged in, in seconds
|
|
207
|
-
expires: 60 * 60 * 24 * 365 * 10,
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const resetPasswordOptions: DbAuthHandlerOptions['resetPassword'] = {
|
|
211
|
-
// handler() is invoked after the password has been successfully updated in
|
|
212
|
-
// the database. Returning anything truthy will automatically log the user
|
|
213
|
-
// in. Return `false` otherwise, and in the Reset Password page redirect the
|
|
214
|
-
// user to the login page.
|
|
215
|
-
handler: (_user) => {
|
|
216
|
-
return true
|
|
217
|
-
},
|
|
218
|
-
|
|
219
|
-
// If `false` then the new password MUST be different from the current one
|
|
220
|
-
allowReusedPassword: true,
|
|
221
|
-
|
|
222
|
-
errors: {
|
|
223
|
-
// the resetToken is valid, but expired
|
|
224
|
-
resetTokenExpired: 'resetToken is expired',
|
|
225
|
-
// no user was found with the given resetToken
|
|
226
|
-
resetTokenInvalid: 'resetToken is invalid',
|
|
227
|
-
// the resetToken was not present in the URL
|
|
228
|
-
resetTokenRequired: 'resetToken is required',
|
|
229
|
-
// new password is the same as the old password (apparently they did not forget it)
|
|
230
|
-
reusedPassword: 'Must choose a new password',
|
|
231
|
-
},
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const changePasswordOptions: DbAuthHandlerOptions['changePassword'] = {
|
|
235
|
-
// handler() is invoked after a logged in user has entered their current
|
|
236
|
-
// password correctly and the new password has been saved to the database.
|
|
237
|
-
// This is a good place to let the user know, by email for example, that
|
|
238
|
-
// their password was changed.
|
|
239
|
-
//
|
|
240
|
-
// Returning anything truthy keeps the user logged in. Return `false` to
|
|
241
|
-
// log them out, and in the Change Password page redirect the user to the
|
|
242
|
-
// login page.
|
|
243
|
-
handler: (_user) => {
|
|
244
|
-
// TODO: Send an email/message to the user letting them know that their
|
|
245
|
-
// password was changed. When you implement this, change `_user` to
|
|
246
|
-
// `user` in the function arguments above.
|
|
247
|
-
|
|
248
|
-
return true
|
|
249
|
-
},
|
|
250
|
-
|
|
251
|
-
// If `false` then the new password MUST be different from the current one
|
|
252
|
-
allowReusedPassword: false,
|
|
253
|
-
|
|
254
|
-
errors: {
|
|
255
|
-
// the current password was not provided
|
|
256
|
-
currentPasswordRequired: 'Current password is required',
|
|
257
|
-
// the new password was not provided
|
|
258
|
-
newPasswordRequired: 'New password is required',
|
|
259
|
-
// the current password is not correct
|
|
260
|
-
incorrectCurrentPassword: 'Current password is incorrect',
|
|
261
|
-
// the user doesn't have a password yet (they signed up with a third
|
|
262
|
-
// party login provider, for example)
|
|
263
|
-
passwordNotSet:
|
|
264
|
-
'This account does not have a password. Use "Forgot password" to set one',
|
|
265
|
-
// new password is the same as the current password
|
|
266
|
-
reusedPassword: 'Must choose a new password',
|
|
267
|
-
},
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
interface UserAttributes {
|
|
271
|
-
name: string
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const signupOptions: DbAuthHandlerOptions<
|
|
275
|
-
UserType,
|
|
276
|
-
UserAttributes
|
|
277
|
-
>['signup'] = {
|
|
278
|
-
// Whatever you want to happen to your data on new user signup. Cedar will
|
|
279
|
-
// check for duplicate usernames before calling this handler. At a minimum
|
|
280
|
-
// you need to save the `username`, `hashedPassword` and `salt` to your
|
|
281
|
-
// user table. `userAttributes` contains any additional object members that
|
|
282
|
-
// were included in the object given to the `signUp()` function you got
|
|
283
|
-
// from `useAuth()`.
|
|
284
|
-
//
|
|
285
|
-
// If you want the user to be immediately logged in, return the user that
|
|
286
|
-
// was created.
|
|
287
|
-
//
|
|
288
|
-
// If this handler throws an error, it will be returned by the `signUp()`
|
|
289
|
-
// function in the form of: `{ error: 'Error message' }`.
|
|
290
|
-
//
|
|
291
|
-
// If this returns anything else, it will be returned by the
|
|
292
|
-
// `signUp()` function in the form of: `{ message: 'String here' }`.
|
|
293
|
-
handler: ({
|
|
294
|
-
username,
|
|
295
|
-
hashedPassword,
|
|
296
|
-
salt,
|
|
297
|
-
userAttributes: _userAttributes,
|
|
298
|
-
}) => {
|
|
299
|
-
return db.user.create({
|
|
300
|
-
data: {
|
|
301
|
-
email: username,
|
|
302
|
-
hashedPassword: hashedPassword,
|
|
303
|
-
salt: salt,
|
|
304
|
-
// name: userAttributes.name
|
|
305
|
-
},
|
|
306
|
-
})
|
|
307
|
-
},
|
|
308
|
-
|
|
309
|
-
// Include any format checks for password here. Return `true` if the
|
|
310
|
-
// password is valid, otherwise throw a `PasswordValidationError`.
|
|
311
|
-
// Import the error along with `DbAuthHandler` from `@cedarjs/api` above.
|
|
312
|
-
passwordValidation: (_password) => {
|
|
313
|
-
return true
|
|
314
|
-
},
|
|
315
|
-
|
|
316
|
-
errors: {
|
|
317
|
-
// `field` will be either "username" or "password"
|
|
318
|
-
fieldMissing: '${field} is required',
|
|
319
|
-
usernameTaken: 'Username `${username}` already in use',
|
|
320
|
-
},
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const authHandler = new DbAuthHandler(event, context, {
|
|
324
|
-
// Provide prisma db client
|
|
325
|
-
db: db,
|
|
326
|
-
|
|
327
|
-
// The name of the property you'd call on `db` to access your user table.
|
|
328
|
-
// i.e. if your Prisma model is named `User` this value would be `user`, as in `db.user`
|
|
329
|
-
authModelAccessor: 'user',
|
|
330
|
-
|
|
331
|
-
// A map of what dbAuth calls a field to what your database calls it.
|
|
332
|
-
// `id` is whatever column you use to uniquely identify a user (probably
|
|
333
|
-
// something like `id` or `userId` or even `email`)
|
|
334
|
-
authFields: {
|
|
335
|
-
id: 'id',
|
|
336
|
-
username: 'email',
|
|
337
|
-
hashedPassword: 'hashedPassword',
|
|
338
|
-
salt: 'salt',
|
|
339
|
-
resetToken: 'resetToken',
|
|
340
|
-
resetTokenExpiresAt: 'resetTokenExpiresAt',
|
|
341
|
-
},
|
|
342
|
-
|
|
343
|
-
// A list of fields on your user object that are safe to return to the
|
|
344
|
-
// client when invoking a handler that returns a user (like forgotPassword
|
|
345
|
-
// and signup). This list should be as small as possible to be sure not to
|
|
346
|
-
// leak any sensitive information to the client.
|
|
347
|
-
allowedUserFields: ['id', 'email'],
|
|
348
|
-
|
|
349
|
-
// Specifies attributes on the cookie that dbAuth sets in order to remember
|
|
350
|
-
// who is logged in. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies
|
|
351
|
-
cookie: {
|
|
352
|
-
attributes: cookieAttributes,
|
|
353
|
-
name: cookieName,
|
|
354
|
-
},
|
|
355
|
-
|
|
356
|
-
changePassword: changePasswordOptions,
|
|
357
|
-
forgotPassword: forgotPasswordOptions,
|
|
358
|
-
login: loginOptions,
|
|
359
|
-
resetPassword: resetPasswordOptions,
|
|
360
|
-
signup: signupOptions,
|
|
361
|
-
})
|
|
362
|
-
|
|
363
|
-
return await authHandler.invoke()
|
|
364
|
-
}
|