@cedarjs/auth-dbauth-setup 0.0.4
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/LICENSE +21 -0
- package/README.md +148 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/setup.d.ts +12 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +42 -0
- package/dist/setupData.d.ts +13 -0
- package/dist/setupData.d.ts.map +1 -0
- package/dist/setupData.js +40 -0
- package/dist/setupHandler.d.ts +7 -0
- package/dist/setupHandler.d.ts.map +1 -0
- package/dist/setupHandler.js +154 -0
- package/dist/shared.d.ts +11 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +91 -0
- package/dist/templates/api/functions/auth.ts.template +207 -0
- package/dist/templates/api/functions/auth.webAuthn.ts.template +225 -0
- package/dist/templates/api/lib/auth.ts.template +121 -0
- package/dist/templates/web/auth.rsc.ts.template +7 -0
- package/dist/templates/web/auth.ts.template +5 -0
- package/dist/templates/web/auth.webAuthn.rsc.ts.template +8 -0
- package/dist/templates/web/auth.webAuthn.ts.template +6 -0
- package/dist/webAuthn.setupData.d.ts +11 -0
- package/dist/webAuthn.setupData.d.ts.map +1 -0
- package/dist/webAuthn.setupData.js +62 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Cedar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Authentication
|
|
2
|
+
|
|
3
|
+
## Contributing
|
|
4
|
+
|
|
5
|
+
If you want to contribute a new auth provider integration we recommend you
|
|
6
|
+
start by implementing it as a custom auth provider in a Redwood App first. When
|
|
7
|
+
that works you can package it up as an npm package and publish it on your own.
|
|
8
|
+
You can then create a PR on this repo with support for your new auth provider
|
|
9
|
+
in our `yarn rw setup auth` cli command. The easiest option is probably to just
|
|
10
|
+
look at one of the existing auth providers in
|
|
11
|
+
`packages/cli/src/commands/setup/auth/providers` and the corresponding
|
|
12
|
+
templates in `../templates`.
|
|
13
|
+
|
|
14
|
+
If you need help setting up a custom auth provider you can read the auth docs
|
|
15
|
+
on the web.
|
|
16
|
+
|
|
17
|
+
### Contributing to the base auth implementation
|
|
18
|
+
|
|
19
|
+
If you want to contribute to our auth implementation, the interface towards
|
|
20
|
+
both auth service providers and RW apps we recommend you start looking in
|
|
21
|
+
`authFactory.ts` and then continue to `AuthProvider.tsx`. `AuthProvider.tsx`
|
|
22
|
+
has most of our implementation together with all the custom hooks it uses.
|
|
23
|
+
Another file to be accustomed with is `AuthContext.ts`. The interface in there
|
|
24
|
+
has pretty good code comments, and is what will be exposed to RW apps.
|
|
25
|
+
|
|
26
|
+
## getCurrentUser
|
|
27
|
+
|
|
28
|
+
`getCurrentUser` returns the user information together with
|
|
29
|
+
an optional collection of roles used by requireAuth() to check if the user is authenticated or has role-based access.
|
|
30
|
+
|
|
31
|
+
Use in conjunction with `requireAuth` in your services to check that a user is logged in, whether or not they are assigned a role, and optionally raise an error if they're not.
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
@param decoded - The decoded access token containing user info and JWT claims like `sub`
|
|
35
|
+
@param { token, SupportedAuthTypes type } - The access token itself as well as the auth provider type
|
|
36
|
+
@param { APIGatewayEvent event, Context context } - An object which contains information from the invoker
|
|
37
|
+
such as headers and cookies, and the context information about the invocation such as IP Address
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Examples
|
|
41
|
+
|
|
42
|
+
#### Checks if currentUser is authenticated
|
|
43
|
+
|
|
44
|
+
This example is the standard use of `getCurrentUser`.
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
export const getCurrentUser = async (
|
|
48
|
+
decoded,
|
|
49
|
+
{ _token, _type },
|
|
50
|
+
{ _event, _context },
|
|
51
|
+
) => {
|
|
52
|
+
return { ...decoded, roles: parseJWT({ decoded }).roles }
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
#### User details fetched via database query
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
export const getCurrentUser = async (decoded) => {
|
|
60
|
+
return await db.user.findUnique({ where: { decoded.email } })
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
#### User info is decoded from the access token
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
export const getCurrentUser = async (decoded) => {
|
|
68
|
+
return { ...decoded }
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### User info is contained in the decoded token and roles extracted
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
export const getCurrentUser = async (decoded) => {
|
|
76
|
+
return { ...decoded, roles: parseJWT({ decoded }).roles }
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
#### User record query by email with namespaced app_metadata roles as Auth0 requires custom JWT claims to be namespaced
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
export const getCurrentUser = async (decoded) => {
|
|
84
|
+
const currentUser = await db.user.findUnique({
|
|
85
|
+
where: { email: decoded.email },
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
...currentUser,
|
|
90
|
+
roles: parseJWT({ decoded: decoded, namespace: NAMESPACE }).roles,
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
#### User record query by an identity with app_metadata roles
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
const getCurrentUser = async (decoded) => {
|
|
99
|
+
const currentUser = await db.user.findUnique({
|
|
100
|
+
where: { userIdentity: decoded.sub },
|
|
101
|
+
})
|
|
102
|
+
return {
|
|
103
|
+
...currentUser,
|
|
104
|
+
roles: parseJWT({ decoded: decoded }).roles,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
#### Cookies and other request information are available in the req parameter, just in case
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
const getCurrentUser = async (_decoded, _raw, { event, _context }) => {
|
|
113
|
+
const cookies = cookie(event.headers.cookies)
|
|
114
|
+
const session = cookies['my.cookie.name']
|
|
115
|
+
const currentUser = await db.sessions.findUnique({ where: { id: session } })
|
|
116
|
+
return currentUser
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## requireAuth
|
|
121
|
+
|
|
122
|
+
Use `requireAuth` in your services to check that a user is logged in, whether or not they are assigned a role, and optionally raise an error if they're not.
|
|
123
|
+
|
|
124
|
+
```js
|
|
125
|
+
@param {string=} roles - An optional role or list of roles
|
|
126
|
+
@param {string[]=} roles - An optional list of roles
|
|
127
|
+
|
|
128
|
+
@returns {boolean} - If the currentUser is authenticated (and assigned one of the given roles)
|
|
129
|
+
|
|
130
|
+
@throws {AuthenticationError} - If the currentUser is not authenticated
|
|
131
|
+
@throws {ForbiddenError} If the currentUser is not allowed due to role permissions
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Examples
|
|
135
|
+
|
|
136
|
+
#### Checks if currentUser is authenticated
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
requireAuth()
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### Checks if currentUser is authenticated and assigned one of the given roles
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
requireAuth({ role: 'admin' })
|
|
146
|
+
requireAuth({ role: ['editor', 'author'] })
|
|
147
|
+
requireAuth({ role: ['publisher'] })
|
|
148
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _context;
|
|
4
|
+
var _Object$defineProperty = require("@babel/runtime-corejs3/core-js/object/define-property");
|
|
5
|
+
var _forEachInstanceProperty = require("@babel/runtime-corejs3/core-js/instance/for-each");
|
|
6
|
+
var _Object$keys = require("@babel/runtime-corejs3/core-js/object/keys");
|
|
7
|
+
_Object$defineProperty(exports, "__esModule", {
|
|
8
|
+
value: true
|
|
9
|
+
});
|
|
10
|
+
var _setup = require("./setup");
|
|
11
|
+
_forEachInstanceProperty(_context = _Object$keys(_setup)).call(_context, function (key) {
|
|
12
|
+
if (key === "default" || key === "__esModule") return;
|
|
13
|
+
if (key in exports && exports[key] === _setup[key]) return;
|
|
14
|
+
_Object$defineProperty(exports, key, {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
get: function () {
|
|
17
|
+
return _setup[key];
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
});
|
package/dist/setup.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type yargs from 'yargs';
|
|
2
|
+
export declare const command = "dbAuth";
|
|
3
|
+
export declare const description = "Set up auth for for dbAuth";
|
|
4
|
+
export declare function builder(yargs: yargs.Argv): void;
|
|
5
|
+
export interface Args {
|
|
6
|
+
webauthn: boolean | null;
|
|
7
|
+
createUserModel: boolean | null;
|
|
8
|
+
generateAuthPages: boolean | null;
|
|
9
|
+
force: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare const handler: (options: Args) => Promise<void>;
|
|
12
|
+
//# sourceMappingURL=setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,eAAO,MAAM,OAAO,WAAW,CAAA;AAC/B,eAAO,MAAM,WAAW,+BAA+B,CAAA;AAEvD,wBAAgB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,QAgCxC;AAED,MAAM,WAAW,IAAI;IACnB,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAA;IACxB,eAAe,EAAE,OAAO,GAAG,IAAI,CAAA;IAC/B,iBAAiB,EAAE,OAAO,GAAG,IAAI,CAAA;IACjC,KAAK,EAAE,OAAO,CAAA;CACf;AAED,eAAO,MAAM,OAAO,YAAmB,IAAI,kBAG1C,CAAA"}
|
package/dist/setup.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _Object$defineProperty = require("@babel/runtime-corejs3/core-js/object/define-property");
|
|
4
|
+
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
|
|
5
|
+
_Object$defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports.builder = builder;
|
|
9
|
+
exports.handler = exports.description = exports.command = void 0;
|
|
10
|
+
var _terminalLink = _interopRequireDefault(require("terminal-link"));
|
|
11
|
+
const command = exports.command = 'dbAuth';
|
|
12
|
+
const description = exports.description = 'Set up auth for for dbAuth';
|
|
13
|
+
function builder(yargs) {
|
|
14
|
+
yargs.option('force', {
|
|
15
|
+
alias: 'f',
|
|
16
|
+
default: false,
|
|
17
|
+
description: 'Overwrite existing configuration',
|
|
18
|
+
type: 'boolean'
|
|
19
|
+
}).option('webauthn', {
|
|
20
|
+
alias: 'w',
|
|
21
|
+
default: null,
|
|
22
|
+
description: 'Include WebAuthn support (TouchID/FaceID)',
|
|
23
|
+
type: 'boolean'
|
|
24
|
+
}).option('createUserModel', {
|
|
25
|
+
alias: 'u',
|
|
26
|
+
default: null,
|
|
27
|
+
description: 'Create a User database model',
|
|
28
|
+
type: 'boolean'
|
|
29
|
+
}).option('generateAuthPages', {
|
|
30
|
+
alias: 'g',
|
|
31
|
+
default: null,
|
|
32
|
+
description: 'Generate auth pages (login, signup, etc.)',
|
|
33
|
+
type: 'boolean'
|
|
34
|
+
}).epilogue(`Also see the ${(0, _terminalLink.default)('Redwood CLI Reference', 'https://redwoodjs.com/docs/cli-commands#setup-auth')}`);
|
|
35
|
+
}
|
|
36
|
+
const handler = async options => {
|
|
37
|
+
const {
|
|
38
|
+
handler
|
|
39
|
+
} = await import('./setupHandler.js');
|
|
40
|
+
return handler(options);
|
|
41
|
+
};
|
|
42
|
+
exports.handler = handler;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AuthGeneratorCtx } from '@cedarjs/cli-helpers/src/auth/authTasks';
|
|
2
|
+
export declare const extraTask: {
|
|
3
|
+
title: string;
|
|
4
|
+
task: () => void;
|
|
5
|
+
};
|
|
6
|
+
export declare const createUserModelTask: {
|
|
7
|
+
title: string;
|
|
8
|
+
task: (ctx: AuthGeneratorCtx) => Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
export declare const notes: string[];
|
|
11
|
+
export declare const notesCreatedUserModel: string[];
|
|
12
|
+
export declare const noteGenerate: string[];
|
|
13
|
+
//# sourceMappingURL=setupData.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setupData.d.ts","sourceRoot":"","sources":["../src/setupData.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAA;AAM/E,eAAO,MAAM,SAAS;;;CAIrB,CAAA;AAED,eAAO,MAAM,mBAAmB;;gBAEZ,gBAAgB;CAoBnC,CAAA;AAGD,eAAO,MAAM,KAAK,UAsDjB,CAAA;AAED,eAAO,MAAM,qBAAqB,UAwBjC,CAAA;AAED,eAAO,MAAM,YAAY,UAMxB,CAAA"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _Object$defineProperty = require("@babel/runtime-corejs3/core-js/object/define-property");
|
|
4
|
+
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
|
|
5
|
+
_Object$defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports.notesCreatedUserModel = exports.notes = exports.noteGenerate = exports.extraTask = exports.createUserModelTask = void 0;
|
|
9
|
+
var _nodeCrypto = _interopRequireDefault(require("node:crypto"));
|
|
10
|
+
var _path = _interopRequireDefault(require("path"));
|
|
11
|
+
var _cliHelpers = require("@cedarjs/cli-helpers");
|
|
12
|
+
var _shared = require("./shared");
|
|
13
|
+
const secret = _nodeCrypto.default.randomBytes(32).toString('base64');
|
|
14
|
+
const extraTask = exports.extraTask = (0, _cliHelpers.addEnvVarTask)('SESSION_SECRET', secret, 'Used to encrypt/decrypt session cookies. Change this value and re-deploy to log out all users of your app at once.');
|
|
15
|
+
const createUserModelTask = exports.createUserModelTask = {
|
|
16
|
+
title: 'Creating model `User`...',
|
|
17
|
+
task: async ctx => {
|
|
18
|
+
const hasUserModel = await (0, _shared.hasModel)('User');
|
|
19
|
+
if (hasUserModel && !ctx.force) {
|
|
20
|
+
throw new Error('User model already exists');
|
|
21
|
+
}
|
|
22
|
+
(0, _shared.addModels)(`
|
|
23
|
+
model User {
|
|
24
|
+
id Int @id @default(autoincrement())
|
|
25
|
+
email String @unique
|
|
26
|
+
hashedPassword String
|
|
27
|
+
salt String
|
|
28
|
+
resetToken String?
|
|
29
|
+
resetTokenExpiresAt DateTime?
|
|
30
|
+
createdAt DateTime @default(now())
|
|
31
|
+
updatedAt DateTime @updatedAt
|
|
32
|
+
}
|
|
33
|
+
`);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// any notes to print out when the job is done
|
|
38
|
+
const notes = exports.notes = [`${_cliHelpers.colors.warning('Done! But you have a little more work to do:')}\n`, 'You will need to add a couple of fields to your User table in order', 'to store a hashed password and salt:', '', ' model User {', ' id Int @id @default(autoincrement())', ' email String @unique', ' hashedPassword String // <─┐', ' salt String // <─┼─ add these lines', ' resetToken String? // <─┤', ' resetTokenExpiresAt DateTime? // <─┘', ' }', '', 'If you already have existing user records you will need to provide', 'a default value for `hashedPassword` and `salt` or Prisma complains, so', 'change those to: ', '', ' hashedPassword String @default("")', ' salt String @default("")', '', 'If you expose any of your user data via GraphQL be sure to exclude', '`hashedPassword` and `salt` (or whatever you named them) from the', 'SDL file that defines the fields for your user.', '', "You'll need to let Redwood know what fields you're using for your", "users' `id` and `username` fields. In this case we're using `id` and", '`email`, so update those in the `authFields` config in', `\`${_shared.functionsPath}/auth.js\`. This is also the place to tell Redwood if`, 'you used a different name for the `hashedPassword`, `salt`,', '`resetToken` or `resetTokenExpiresAt`, fields:`', '', ' authFields: {', " id: 'id',", " username: 'email',", " hashedPassword: 'hashedPassword',", " salt: 'salt',", " resetToken: 'resetToken',", " resetTokenExpiresAt: 'resetTokenExpiresAt',", ' },', '', "To get the actual user that's logged in, take a look at `getCurrentUser()`", `in \`${_shared.libPath}/auth.js\`. We default it to something simple, but you may`, 'use different names for your model or unique ID fields, in which case you', 'need to update those calls (instructions are in the comment above the code).', '', 'Finally, we created a SESSION_SECRET environment variable for you in', `${_path.default.join((0, _cliHelpers.getPaths)().base, '.env')}. This value should NOT be checked`, 'into version control and should be unique for each environment you', 'deploy to. If you ever need to log everyone out of your app at once', 'change this secret to a new value and deploy. To create a new secret, run:', '', ' yarn rw generate secret', ''];
|
|
39
|
+
const notesCreatedUserModel = exports.notesCreatedUserModel = [`${_cliHelpers.colors.warning('Done! But you have a little more work to do:')}\n`, 'If you expose any of your user data via GraphQL be sure to exclude', '`hashedPassword` and `salt` (or whatever you named them) from the', 'SDL file that defines the fields for your user.', '', "To get the actual user that's logged in, take a look at `getCurrentUser()`", `in \`${_shared.libPath}/auth.js\`. We default it to something simple, but you may`, 'use different names for your model or unique ID fields, in which case you', 'need to update those calls (instructions are in the comment above the code).', '', 'Finally, we created a SESSION_SECRET environment variable for you in', `${_path.default.join((0, _cliHelpers.getPaths)().base, '.env')}. This value should NOT be checked`, 'into version control and should be unique for each environment you', 'deploy to. If you ever need to log everyone out of your app at once', 'change this secret to a new value and deploy. To create a new secret, run:', '', ' yarn rw generate secret', '', "A new User model was added to your schema. Don't forget to migrate your db", 'before you try using dbAuth:', '', ' yarn rw prisma migrate dev', ''];
|
|
40
|
+
const noteGenerate = exports.noteGenerate = ['', "Need simple Login, Signup and Forgot Password pages? We've got a generator", 'for those as well:', '', ' yarn rw generate dbAuth'];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Args } from './setup';
|
|
2
|
+
export declare function handler({ webauthn, createUserModel, generateAuthPages, force: forceArg, }: Args): Promise<void>;
|
|
3
|
+
export declare const createAuthDecoderFunction: {
|
|
4
|
+
title: string;
|
|
5
|
+
task: () => void;
|
|
6
|
+
};
|
|
7
|
+
//# sourceMappingURL=setupHandler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setupHandler.d.ts","sourceRoot":"","sources":["../src/setupHandler.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAuBnC,wBAAsB,OAAO,CAAC,EAC5B,QAAQ,EACR,eAAe,EACf,iBAAiB,EACjB,KAAK,EAAE,QAAQ,GAChB,EAAE,IAAI,iBAoEN;AAsED,eAAO,MAAM,yBAAyB;;;CAuCrC,CAAA"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _Object$defineProperty = require("@babel/runtime-corejs3/core-js/object/define-property");
|
|
4
|
+
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
|
|
5
|
+
_Object$defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports.createAuthDecoderFunction = void 0;
|
|
9
|
+
exports.handler = handler;
|
|
10
|
+
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/includes"));
|
|
11
|
+
require("core-js/modules/es.array.push.js");
|
|
12
|
+
require("core-js/modules/esnext.json.parse.js");
|
|
13
|
+
var _fs = _interopRequireDefault(require("fs"));
|
|
14
|
+
var _path = _interopRequireDefault(require("path"));
|
|
15
|
+
var _prompts = _interopRequireDefault(require("prompts"));
|
|
16
|
+
var _cliHelpers = require("@cedarjs/cli-helpers");
|
|
17
|
+
var _setupData = require("./setupData");
|
|
18
|
+
var _shared = require("./shared");
|
|
19
|
+
var _webAuthn = require("./webAuthn.setupData");
|
|
20
|
+
async function handler({
|
|
21
|
+
webauthn,
|
|
22
|
+
createUserModel,
|
|
23
|
+
generateAuthPages,
|
|
24
|
+
force: forceArg
|
|
25
|
+
}) {
|
|
26
|
+
const {
|
|
27
|
+
version
|
|
28
|
+
} = JSON.parse(_fs.default.readFileSync(_path.default.resolve(__dirname, '../package.json'), 'utf-8'));
|
|
29
|
+
const webAuthn = await shouldIncludeWebAuthn(webauthn);
|
|
30
|
+
const createDbUserModel = await shouldCreateUserModel(createUserModel);
|
|
31
|
+
const generateDbAuthPages = await shouldGenerateDbAuthPages(generateAuthPages);
|
|
32
|
+
const oneMoreThing = [];
|
|
33
|
+
if (webAuthn) {
|
|
34
|
+
if (createDbUserModel) {
|
|
35
|
+
oneMoreThing.push(..._setupData.notesCreatedUserModel);
|
|
36
|
+
} else {
|
|
37
|
+
oneMoreThing.push(..._webAuthn.notes);
|
|
38
|
+
}
|
|
39
|
+
if (!generateDbAuthPages) {
|
|
40
|
+
oneMoreThing.push(..._webAuthn.noteGenerate);
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
if (createDbUserModel) {
|
|
44
|
+
oneMoreThing.push(..._setupData.notesCreatedUserModel);
|
|
45
|
+
} else {
|
|
46
|
+
oneMoreThing.push(..._setupData.notes);
|
|
47
|
+
}
|
|
48
|
+
if (!generateDbAuthPages) {
|
|
49
|
+
oneMoreThing.push(..._setupData.noteGenerate);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
let createDbUserModelTask = undefined;
|
|
53
|
+
if (createDbUserModel) {
|
|
54
|
+
if (webAuthn) {
|
|
55
|
+
createDbUserModelTask = _webAuthn.createUserModelTask;
|
|
56
|
+
} else {
|
|
57
|
+
createDbUserModelTask = _setupData.createUserModelTask;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
await (0, _cliHelpers.standardAuthHandler)({
|
|
61
|
+
basedir: __dirname,
|
|
62
|
+
forceArg,
|
|
63
|
+
provider: 'dbAuth',
|
|
64
|
+
authDecoderImport: "import { createAuthDecoder } from '@cedarjs/auth-dbauth-api'",
|
|
65
|
+
webAuthn,
|
|
66
|
+
webPackages: [`@cedarjs/auth-dbauth-web@${version}`, ...(webAuthn ? _webAuthn.webPackages : [])],
|
|
67
|
+
apiPackages: [`@cedarjs/auth-dbauth-api@${version}`, ...(webAuthn ? _webAuthn.apiPackages : [])],
|
|
68
|
+
extraTasks: [webAuthn ? _webAuthn.extraTask : _setupData.extraTask, createDbUserModelTask, createAuthDecoderFunction, generateDbAuthPages ? (0, _shared.generateAuthPagesTask)(createDbUserModel) : undefined],
|
|
69
|
+
notes: oneMoreThing
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Prompt the user (unless already specified on the command line) if they want
|
|
75
|
+
* to enable WebAuthn support
|
|
76
|
+
*/
|
|
77
|
+
async function shouldIncludeWebAuthn(webauthn) {
|
|
78
|
+
if (webauthn === null) {
|
|
79
|
+
const webAuthnResponse = await (0, _prompts.default)({
|
|
80
|
+
type: 'confirm',
|
|
81
|
+
name: 'answer',
|
|
82
|
+
message: `Enable WebAuthn support (TouchID/FaceID)? See https://redwoodjs.com/docs/auth/dbAuth#webAuthn`,
|
|
83
|
+
initial: false
|
|
84
|
+
});
|
|
85
|
+
return webAuthnResponse.answer;
|
|
86
|
+
}
|
|
87
|
+
return webauthn;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Prompt the user (unless already specified on the command line) if they want
|
|
92
|
+
* to create a User model in their Prisma schema
|
|
93
|
+
*/
|
|
94
|
+
async function shouldCreateUserModel(createUserModel) {
|
|
95
|
+
const hasUserModel = await (0, _shared.hasModel)('User');
|
|
96
|
+
const modelNames = await (0, _shared.getModelNames)();
|
|
97
|
+
const isNewProject = modelNames.length === 1 && modelNames[0] === 'UserExample';
|
|
98
|
+
if (isNewProject) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
if (createUserModel === null && !hasUserModel) {
|
|
102
|
+
const createModelResponse = await (0, _prompts.default)({
|
|
103
|
+
type: 'confirm',
|
|
104
|
+
name: 'answer',
|
|
105
|
+
message: 'Create User model?',
|
|
106
|
+
initial: false
|
|
107
|
+
});
|
|
108
|
+
return createModelResponse.answer;
|
|
109
|
+
}
|
|
110
|
+
return createUserModel;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Prompt the user (unless already specified on the command line) if they want
|
|
115
|
+
* to generate auth pages. Also checks to make sure auth pages don't already
|
|
116
|
+
* exist before prompting.
|
|
117
|
+
*/
|
|
118
|
+
async function shouldGenerateDbAuthPages(generateAuthPages) {
|
|
119
|
+
if (generateAuthPages === null && !(0, _shared.hasAuthPages)()) {
|
|
120
|
+
const generateAuthPagesResponse = await (0, _prompts.default)({
|
|
121
|
+
type: 'confirm',
|
|
122
|
+
name: 'answer',
|
|
123
|
+
message: 'Generate auth pages (login, signup, forgotten password, etc)?',
|
|
124
|
+
initial: false
|
|
125
|
+
});
|
|
126
|
+
return generateAuthPagesResponse.answer;
|
|
127
|
+
}
|
|
128
|
+
return generateAuthPages;
|
|
129
|
+
}
|
|
130
|
+
const createAuthDecoderFunction = exports.createAuthDecoderFunction = {
|
|
131
|
+
title: 'Create auth decoder function',
|
|
132
|
+
task: () => {
|
|
133
|
+
const graphqlPath = (0, _cliHelpers.getGraphqlPath)();
|
|
134
|
+
if (!graphqlPath) {
|
|
135
|
+
throw new Error('Could not find your graphql file path');
|
|
136
|
+
}
|
|
137
|
+
const authDecoderCreation = 'const authDecoder = createAuthDecoder(cookieName)';
|
|
138
|
+
const content = _fs.default.readFileSync(graphqlPath, 'utf-8');
|
|
139
|
+
let newContent = content.replace('import { getCurrentUser } from', 'import { cookieName, getCurrentUser } from');
|
|
140
|
+
const authDecoderCreationRegexp = new RegExp('^' + escapeRegExp(authDecoderCreation), 'm');
|
|
141
|
+
if (!authDecoderCreationRegexp.test(newContent)) {
|
|
142
|
+
newContent = newContent.replace('export const handler = createGraphQLHandler({', authDecoderCreation + '\n\n' + 'export const handler = createGraphQLHandler({');
|
|
143
|
+
}
|
|
144
|
+
if (!(0, _includes.default)(newContent).call(newContent, 'import { cookieName')) {
|
|
145
|
+
throw new Error('Failed to import cookieName');
|
|
146
|
+
}
|
|
147
|
+
_fs.default.writeFileSync(graphqlPath, newContent);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
|
|
152
|
+
function escapeRegExp(str) {
|
|
153
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
|
154
|
+
}
|
package/dist/shared.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const libPath: string;
|
|
2
|
+
export declare const functionsPath: string;
|
|
3
|
+
export declare const getModelNames: () => Promise<string[]>;
|
|
4
|
+
export declare const hasModel: (name: string) => Promise<boolean>;
|
|
5
|
+
export declare function addModels(models: string): Promise<void>;
|
|
6
|
+
export declare function hasAuthPages(): boolean;
|
|
7
|
+
export declare function generateAuthPagesTask(generatingUserModel: boolean): {
|
|
8
|
+
title: string;
|
|
9
|
+
task: () => Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,OAAO,QAAkD,CAAA;AACtE,eAAO,MAAM,aAAa,QAGzB,CAAA;AAED,eAAO,MAAM,aAAa,yBAKzB,CAAA;AAED,eAAO,MAAM,QAAQ,SAAgB,MAAM,qBAe1C,CAAA;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,iBAQ7C;AAED,wBAAgB,YAAY,YAuB3B;AAED,wBAAgB,qBAAqB,CAAC,mBAAmB,EAAE,OAAO;;;EAwBjE"}
|
package/dist/shared.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _Object$defineProperty = require("@babel/runtime-corejs3/core-js/object/define-property");
|
|
4
|
+
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
|
|
5
|
+
_Object$defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports.addModels = addModels;
|
|
9
|
+
exports.functionsPath = void 0;
|
|
10
|
+
exports.generateAuthPagesTask = generateAuthPagesTask;
|
|
11
|
+
exports.getModelNames = void 0;
|
|
12
|
+
exports.hasAuthPages = hasAuthPages;
|
|
13
|
+
exports.libPath = exports.hasModel = void 0;
|
|
14
|
+
require("core-js/modules/es.array.push.js");
|
|
15
|
+
var _map = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/map"));
|
|
16
|
+
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/includes"));
|
|
17
|
+
var _some = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/instance/some"));
|
|
18
|
+
var _nodeFs = _interopRequireDefault(require("node:fs"));
|
|
19
|
+
var _nodePath = _interopRequireDefault(require("node:path"));
|
|
20
|
+
var _internals = require("@prisma/internals");
|
|
21
|
+
var _execa = _interopRequireDefault(require("execa"));
|
|
22
|
+
var _cliHelpers = require("@cedarjs/cli-helpers");
|
|
23
|
+
var _projectConfig = require("@cedarjs/project-config");
|
|
24
|
+
const libPath = exports.libPath = (0, _cliHelpers.getPaths)().api.lib.replace((0, _cliHelpers.getPaths)().base, '');
|
|
25
|
+
const functionsPath = exports.functionsPath = (0, _cliHelpers.getPaths)().api.functions.replace((0, _cliHelpers.getPaths)().base, '');
|
|
26
|
+
const getModelNames = async () => {
|
|
27
|
+
var _context;
|
|
28
|
+
const datamodel = await (0, _internals.getSchema)((0, _cliHelpers.getPaths)().api.dbSchema);
|
|
29
|
+
const schema = await (0, _internals.getDMMF)({
|
|
30
|
+
datamodel
|
|
31
|
+
});
|
|
32
|
+
return (0, _map.default)(_context = schema.datamodel.models).call(_context, model => model.name);
|
|
33
|
+
};
|
|
34
|
+
exports.getModelNames = getModelNames;
|
|
35
|
+
const hasModel = async name => {
|
|
36
|
+
var _context2;
|
|
37
|
+
if (!name) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Support PascalCase, camelCase, kebab-case, UPPER_CASE, and lowercase model
|
|
42
|
+
// names
|
|
43
|
+
const modelName = name.replace(/[_-]/g, '').toLowerCase();
|
|
44
|
+
const modelNames = (0, _map.default)(_context2 = await getModelNames()).call(_context2, name => name.toLowerCase());
|
|
45
|
+
if ((0, _includes.default)(modelNames).call(modelNames, modelName)) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
};
|
|
50
|
+
exports.hasModel = hasModel;
|
|
51
|
+
async function addModels(models) {
|
|
52
|
+
const isDirectory = _nodeFs.default.statSync((0, _cliHelpers.getPaths)().api.dbSchema).isDirectory();
|
|
53
|
+
if (isDirectory) {
|
|
54
|
+
_nodeFs.default.writeFileSync(_nodePath.default.join((0, _cliHelpers.getPaths)().api.dbSchema, 'user.prisma'), models);
|
|
55
|
+
} else {
|
|
56
|
+
_nodeFs.default.appendFileSync((0, _cliHelpers.getPaths)().api.dbSchema, models);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function hasAuthPages() {
|
|
60
|
+
var _context3;
|
|
61
|
+
const routes = _nodeFs.default.readFileSync((0, _cliHelpers.getPaths)().web.routes, 'utf-8');
|
|
62
|
+
|
|
63
|
+
// If the user already has a route for /login, /signin, or /signup, we
|
|
64
|
+
// assume auth pages are already set up
|
|
65
|
+
if (/path={?['"]\/(login|signin|signup)['"]}? /i.test(routes)) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return (0, _some.default)(_context3 = (0, _projectConfig.processPagesDir)()).call(_context3, page => {
|
|
69
|
+
if (page.importName === 'LoginPage' || page.importName === 'LogInPage' || page.importName === 'SigninPage' || page.importName === 'SignInPage' || page.importName === 'SignupPage' || page.importName === 'SignUpPage') {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function generateAuthPagesTask(generatingUserModel) {
|
|
76
|
+
return {
|
|
77
|
+
title: 'Adding dbAuth pages...',
|
|
78
|
+
task: async () => {
|
|
79
|
+
const rwjsPaths = (0, _cliHelpers.getPaths)();
|
|
80
|
+
const args = ['rw', 'g', 'dbAuth'];
|
|
81
|
+
if (generatingUserModel) {
|
|
82
|
+
args.push('--username-label', 'username', '--password-label', 'password');
|
|
83
|
+
}
|
|
84
|
+
await (0, _execa.default)('yarn', args, {
|
|
85
|
+
stdio: 'inherit',
|
|
86
|
+
shell: true,
|
|
87
|
+
cwd: rwjsPaths.base
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|