@cedarjs/auth-dbauth-web 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/dbAuth.d.ts +42 -0
- package/dist/dbAuth.d.ts.map +1 -0
- package/dist/dbAuth.js +193 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/webAuthn.d.ts +13 -0
- package/dist/webAuthn.d.ts.map +1 -0
- package/dist/webAuthn.js +167 -0
- package/package.json +45 -0
- package/webAuthn/index.js +2 -0
- package/webAuthn/package.json +4 -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/dbAuth.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { CurrentUser } from '@cedarjs/auth';
|
|
2
|
+
import type { WebAuthnClientType } from './webAuthn';
|
|
3
|
+
export interface LoginAttributes {
|
|
4
|
+
username: string;
|
|
5
|
+
password: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ResetPasswordAttributes {
|
|
8
|
+
resetToken: string;
|
|
9
|
+
password: string;
|
|
10
|
+
}
|
|
11
|
+
export type SignupAttributes = Record<string, unknown> & LoginAttributes;
|
|
12
|
+
export declare function createAuth(dbAuthClient: ReturnType<typeof createDbAuthClient>, customProviderHooks?: {
|
|
13
|
+
useCurrentUser?: () => Promise<CurrentUser>;
|
|
14
|
+
useHasRole?: (currentUser: CurrentUser | null) => (rolesToCheck: string | string[]) => boolean;
|
|
15
|
+
}): {
|
|
16
|
+
AuthContext: import("react").Context<import("@cedarjs/auth").AuthContextInterface<string | (() => void), LoginAttributes, any, unknown, boolean, SignupAttributes, any, any, ResetPasswordAttributes, any, any, import("./webAuthn").default> | undefined>;
|
|
17
|
+
AuthProvider: ({ children }: import("@cedarjs/auth").AuthProviderProps) => import("react").JSX.Element;
|
|
18
|
+
useAuth: () => import("@cedarjs/auth").AuthContextInterface<string | (() => void), LoginAttributes, any, unknown, boolean, SignupAttributes, any, any, ResetPasswordAttributes, any, any, import("./webAuthn").default>;
|
|
19
|
+
};
|
|
20
|
+
export interface DbAuthClientArgs {
|
|
21
|
+
webAuthn?: InstanceType<WebAuthnClientType>;
|
|
22
|
+
dbAuthUrl?: string;
|
|
23
|
+
fetchConfig?: {
|
|
24
|
+
credentials?: 'include' | 'same-origin';
|
|
25
|
+
};
|
|
26
|
+
middleware?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export declare function createDbAuthClient({ webAuthn, dbAuthUrl, fetchConfig, middleware, }?: DbAuthClientArgs): {
|
|
29
|
+
type: string;
|
|
30
|
+
client: import("./webAuthn").default | undefined;
|
|
31
|
+
login: ({ username, password }: LoginAttributes) => Promise<any>;
|
|
32
|
+
logout: () => Promise<boolean>;
|
|
33
|
+
signup: (attributes: SignupAttributes) => Promise<any>;
|
|
34
|
+
getToken: () => Promise<string | null>;
|
|
35
|
+
getUserMetadata: () => Promise<string | (() => void) | null>;
|
|
36
|
+
forgotPassword: (username: string) => Promise<any>;
|
|
37
|
+
resetPassword: (attributes: ResetPasswordAttributes) => Promise<any>;
|
|
38
|
+
validateResetToken: (resetToken: string | null) => Promise<any>;
|
|
39
|
+
getAuthUrl: () => string;
|
|
40
|
+
middlewareAuthEnabled: boolean;
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=dbAuth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dbAuth.d.ts","sourceRoot":"","sources":["../src/dbAuth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAuB,MAAM,eAAe,CAAA;AAMrE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe,CAAA;AAkBxE,wBAAgB,UAAU,CACxB,YAAY,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,EACnD,mBAAmB,CAAC,EAAE;IACpB,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAA;IAC3C,UAAU,CAAC,EAAE,CACX,WAAW,EAAE,WAAW,GAAG,IAAI,KAC5B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,OAAO,CAAA;CAClD;;wCAEG,eAAgC,+BACb,OAAO;;EAI/B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,YAAY,CAAC,kBAAkB,CAAC,CAAA;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE;QACZ,WAAW,CAAC,EAAE,SAAS,GAAG,aAAa,CAAA;KACxC,CAAA;IACD,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,SAAS,EACT,WAAW,EACX,UAA4C,GAC7C,GAAE,gBAAqB;;;oCA6EuB,eAAe;;yBAgC1B,gBAAgB;;;+BA9EV,MAAM;gCAmEL,uBAAuB;qCAsBlB,MAAM,GAAG,IAAI;;;EAoC5D"}
|
package/dist/dbAuth.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
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.createAuth = createAuth;
|
|
9
|
+
exports.createDbAuthClient = createDbAuthClient;
|
|
10
|
+
var _stringify = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/json/stringify"));
|
|
11
|
+
var _auth = require("@cedarjs/auth");
|
|
12
|
+
const TOKEN_CACHE_TIME = 5000;
|
|
13
|
+
|
|
14
|
+
// This is the middleware-edition auth function
|
|
15
|
+
// Overrides the default getCurrentUser to fetch it from middleware instead
|
|
16
|
+
function createMiddlewareAuth(dbAuthClient, customProviderHooks) {
|
|
17
|
+
return (0, _auth.createAuthentication)(dbAuthClient, {
|
|
18
|
+
...customProviderHooks,
|
|
19
|
+
useCurrentUser: customProviderHooks?.useCurrentUser ?? (() => (0, _auth.getCurrentUserFromMiddleware)(dbAuthClient.getAuthUrl()))
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function createAuth(dbAuthClient, customProviderHooks) {
|
|
23
|
+
if (dbAuthClient.middlewareAuthEnabled) {
|
|
24
|
+
return createMiddlewareAuth(dbAuthClient, customProviderHooks);
|
|
25
|
+
}
|
|
26
|
+
return (0, _auth.createAuthentication)(dbAuthClient, customProviderHooks);
|
|
27
|
+
}
|
|
28
|
+
function createDbAuthClient({
|
|
29
|
+
webAuthn,
|
|
30
|
+
dbAuthUrl,
|
|
31
|
+
fetchConfig,
|
|
32
|
+
middleware = RWJS_ENV.RWJS_EXP_STREAMING_SSR
|
|
33
|
+
} = {}) {
|
|
34
|
+
const credentials = fetchConfig?.credentials || 'same-origin';
|
|
35
|
+
webAuthn?.setAuthApiUrl(dbAuthUrl);
|
|
36
|
+
let getTokenPromise;
|
|
37
|
+
let lastTokenCheckAt = new Date('1970-01-01T00:00:00');
|
|
38
|
+
let cachedToken;
|
|
39
|
+
const getDbAuthUrl = () => {
|
|
40
|
+
if (dbAuthUrl) {
|
|
41
|
+
return dbAuthUrl;
|
|
42
|
+
}
|
|
43
|
+
return middleware ? `/middleware/dbauth` : `${RWJS_API_URL}/auth`;
|
|
44
|
+
};
|
|
45
|
+
const resetAndFetch = async (...params) => {
|
|
46
|
+
resetTokenCache();
|
|
47
|
+
return fetch(...params);
|
|
48
|
+
};
|
|
49
|
+
const isTokenCacheExpired = () => {
|
|
50
|
+
const now = new Date();
|
|
51
|
+
return now.getTime() - lastTokenCheckAt.getTime() > TOKEN_CACHE_TIME;
|
|
52
|
+
};
|
|
53
|
+
const resetTokenCache = () => {
|
|
54
|
+
lastTokenCheckAt = new Date('1970-01-01T00:00:00');
|
|
55
|
+
cachedToken = null;
|
|
56
|
+
};
|
|
57
|
+
const forgotPassword = async username => {
|
|
58
|
+
const response = await resetAndFetch(getDbAuthUrl(), {
|
|
59
|
+
credentials,
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: {
|
|
62
|
+
'Content-Type': 'application/json'
|
|
63
|
+
},
|
|
64
|
+
body: (0, _stringify.default)({
|
|
65
|
+
username,
|
|
66
|
+
method: 'forgotPassword'
|
|
67
|
+
})
|
|
68
|
+
});
|
|
69
|
+
return response.json();
|
|
70
|
+
};
|
|
71
|
+
const getToken = async () => {
|
|
72
|
+
// Middleware auth providers doesn't need a token
|
|
73
|
+
if (middleware) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Return the existing fetch promise, so that parallel calls
|
|
78
|
+
// to getToken only cause a single fetch
|
|
79
|
+
if (getTokenPromise) {
|
|
80
|
+
return getTokenPromise;
|
|
81
|
+
}
|
|
82
|
+
if (isTokenCacheExpired()) {
|
|
83
|
+
getTokenPromise = fetch(`${getDbAuthUrl()}?method=getToken`, {
|
|
84
|
+
credentials
|
|
85
|
+
}).then(response => response.text()).then(tokenText => {
|
|
86
|
+
lastTokenCheckAt = new Date();
|
|
87
|
+
cachedToken = tokenText.length === 0 ? null : tokenText;
|
|
88
|
+
return cachedToken;
|
|
89
|
+
}).catch(() => {
|
|
90
|
+
return null;
|
|
91
|
+
}).finally(() => {
|
|
92
|
+
getTokenPromise = null;
|
|
93
|
+
});
|
|
94
|
+
return getTokenPromise;
|
|
95
|
+
}
|
|
96
|
+
return cachedToken;
|
|
97
|
+
};
|
|
98
|
+
const login = async ({
|
|
99
|
+
username,
|
|
100
|
+
password
|
|
101
|
+
}) => {
|
|
102
|
+
const response = await resetAndFetch(getDbAuthUrl(), {
|
|
103
|
+
credentials,
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: {
|
|
106
|
+
'Content-Type': 'application/json'
|
|
107
|
+
},
|
|
108
|
+
body: (0, _stringify.default)({
|
|
109
|
+
username,
|
|
110
|
+
password,
|
|
111
|
+
method: 'login'
|
|
112
|
+
})
|
|
113
|
+
});
|
|
114
|
+
return response.json();
|
|
115
|
+
};
|
|
116
|
+
const logout = async () => {
|
|
117
|
+
await resetAndFetch(getDbAuthUrl(), {
|
|
118
|
+
credentials,
|
|
119
|
+
method: 'POST',
|
|
120
|
+
body: (0, _stringify.default)({
|
|
121
|
+
method: 'logout'
|
|
122
|
+
})
|
|
123
|
+
});
|
|
124
|
+
return true;
|
|
125
|
+
};
|
|
126
|
+
const resetPassword = async attributes => {
|
|
127
|
+
const response = await resetAndFetch(getDbAuthUrl(), {
|
|
128
|
+
credentials,
|
|
129
|
+
method: 'POST',
|
|
130
|
+
headers: {
|
|
131
|
+
'Content-Type': 'application/json'
|
|
132
|
+
},
|
|
133
|
+
body: (0, _stringify.default)({
|
|
134
|
+
...attributes,
|
|
135
|
+
method: 'resetPassword'
|
|
136
|
+
})
|
|
137
|
+
});
|
|
138
|
+
return response.json();
|
|
139
|
+
};
|
|
140
|
+
const signup = async attributes => {
|
|
141
|
+
const response = await resetAndFetch(getDbAuthUrl(), {
|
|
142
|
+
credentials,
|
|
143
|
+
method: 'POST',
|
|
144
|
+
headers: {
|
|
145
|
+
'Content-Type': 'application/json'
|
|
146
|
+
},
|
|
147
|
+
body: (0, _stringify.default)({
|
|
148
|
+
...attributes,
|
|
149
|
+
method: 'signup'
|
|
150
|
+
})
|
|
151
|
+
});
|
|
152
|
+
return response.json();
|
|
153
|
+
};
|
|
154
|
+
const validateResetToken = async resetToken => {
|
|
155
|
+
const response = await resetAndFetch(getDbAuthUrl(), {
|
|
156
|
+
credentials,
|
|
157
|
+
method: 'POST',
|
|
158
|
+
headers: {
|
|
159
|
+
'Content-Type': 'application/json'
|
|
160
|
+
},
|
|
161
|
+
body: (0, _stringify.default)({
|
|
162
|
+
resetToken,
|
|
163
|
+
method: 'validateResetToken'
|
|
164
|
+
})
|
|
165
|
+
});
|
|
166
|
+
return response.json();
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* dbAuth doesn't implement the concept of userMetadata. We used to return the
|
|
171
|
+
* token (i.e. userId) as the userMetadata.
|
|
172
|
+
*/
|
|
173
|
+
const getUserMetadata = async () => {
|
|
174
|
+
return middleware ? () => {} : getToken();
|
|
175
|
+
};
|
|
176
|
+
return {
|
|
177
|
+
type: 'dbAuth',
|
|
178
|
+
client: webAuthn,
|
|
179
|
+
login,
|
|
180
|
+
logout,
|
|
181
|
+
signup,
|
|
182
|
+
getToken,
|
|
183
|
+
getUserMetadata,
|
|
184
|
+
forgotPassword,
|
|
185
|
+
resetPassword,
|
|
186
|
+
validateResetToken,
|
|
187
|
+
// New methods for middleware auth:
|
|
188
|
+
// This is so we can get the dbAuthUrl in getCurrentUserFromMiddleware
|
|
189
|
+
getAuthUrl: getDbAuthUrl,
|
|
190
|
+
// This is so that we can skip fetching getCurrentUser in reauthenticate
|
|
191
|
+
middlewareAuthEnabled: middleware
|
|
192
|
+
};
|
|
193
|
+
}
|
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,UAAU,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 _dbAuth = require("./dbAuth");
|
|
11
|
+
_forEachInstanceProperty(_context = _Object$keys(_dbAuth)).call(_context, function (key) {
|
|
12
|
+
if (key === "default" || key === "__esModule") return;
|
|
13
|
+
if (key in exports && exports[key] === _dbAuth[key]) return;
|
|
14
|
+
_Object$defineProperty(exports, key, {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
get: function () {
|
|
17
|
+
return _dbAuth[key];
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export default class WebAuthnClient {
|
|
2
|
+
authApiUrl: string;
|
|
3
|
+
private getAuthApiUrl;
|
|
4
|
+
setAuthApiUrl(authApiUrl?: string): void;
|
|
5
|
+
isSupported(): Promise<boolean>;
|
|
6
|
+
isEnabled(): boolean;
|
|
7
|
+
private authenticationOptions;
|
|
8
|
+
authenticate(): Promise<boolean>;
|
|
9
|
+
private registrationOptions;
|
|
10
|
+
register(): Promise<boolean>;
|
|
11
|
+
}
|
|
12
|
+
export type WebAuthnClientType = typeof WebAuthnClient;
|
|
13
|
+
//# sourceMappingURL=webAuthn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webAuthn.d.ts","sourceRoot":"","sources":["../src/webAuthn.ts"],"names":[],"mappings":"AAqCA,MAAM,CAAC,OAAO,OAAO,cAAc;IACjC,UAAU,SAAK;IAEf,OAAO,CAAC,aAAa;IAIrB,aAAa,CAAC,UAAU,CAAC,EAAE,MAAM;IAM3B,WAAW;IAKjB,SAAS;YAQK,qBAAqB;IAmC7B,YAAY;IA0ClB,OAAO,CAAC,mBAAmB;IA+BrB,QAAQ;CAwCf;AAED,MAAM,MAAM,kBAAkB,GAAG,OAAO,cAAc,CAAA"}
|
package/dist/webAuthn.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
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.default = void 0;
|
|
9
|
+
var _stringify = _interopRequireDefault(require("@babel/runtime-corejs3/core-js/json/stringify"));
|
|
10
|
+
require("core-js/modules/esnext.json.parse.js");
|
|
11
|
+
class WebAuthnRegistrationError extends Error {
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'WebAuthnRegistrationError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
class WebAuthnAuthenticationError extends Error {
|
|
18
|
+
constructor(message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = 'WebAuthnAuthenticationError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
class WebAuthnAlreadyRegisteredError extends WebAuthnRegistrationError {
|
|
24
|
+
constructor() {
|
|
25
|
+
super('This device is already registered');
|
|
26
|
+
this.name = 'WebAuthnAlreadyRegisteredError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
class WebAuthnDeviceNotFoundError extends WebAuthnAuthenticationError {
|
|
30
|
+
constructor() {
|
|
31
|
+
super('WebAuthn device not found');
|
|
32
|
+
this.name = 'WebAuthnDeviceNotFoundError';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
class WebAuthnNoAuthenticatorError extends WebAuthnAuthenticationError {
|
|
36
|
+
constructor() {
|
|
37
|
+
super("This device was not recognized. Use username/password login, or if you're using iOS you can try reloading this page");
|
|
38
|
+
this.name = 'WebAuthnNoAuthenticatorError';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
class WebAuthnClient {
|
|
42
|
+
constructor() {
|
|
43
|
+
this.authApiUrl = '';
|
|
44
|
+
}
|
|
45
|
+
getAuthApiUrl() {
|
|
46
|
+
return this.authApiUrl || `${RWJS_API_URL}/auth`;
|
|
47
|
+
}
|
|
48
|
+
setAuthApiUrl(authApiUrl) {
|
|
49
|
+
if (authApiUrl) {
|
|
50
|
+
this.authApiUrl = authApiUrl;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async isSupported() {
|
|
54
|
+
const {
|
|
55
|
+
browserSupportsWebAuthn
|
|
56
|
+
} = await import('@simplewebauthn/browser');
|
|
57
|
+
return browserSupportsWebAuthn();
|
|
58
|
+
}
|
|
59
|
+
isEnabled() {
|
|
60
|
+
if (typeof window === 'undefined') {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return !!/\bwebAuthn\b/.test(document.cookie);
|
|
64
|
+
}
|
|
65
|
+
async authenticationOptions() {
|
|
66
|
+
let options;
|
|
67
|
+
try {
|
|
68
|
+
const xhr = new XMLHttpRequest();
|
|
69
|
+
xhr.withCredentials = true;
|
|
70
|
+
xhr.open('GET', `${this.getAuthApiUrl()}?method=webAuthnAuthOptions`, false);
|
|
71
|
+
xhr.setRequestHeader('content-type', 'application/json');
|
|
72
|
+
xhr.send(null);
|
|
73
|
+
options = JSON.parse(xhr.responseText);
|
|
74
|
+
if (xhr.status !== 200) {
|
|
75
|
+
if (options.error?.match(/username and password/)) {
|
|
76
|
+
throw new WebAuthnDeviceNotFoundError();
|
|
77
|
+
} else {
|
|
78
|
+
throw new WebAuthnAuthenticationError(`Could not start authentication: ${options.error}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} catch (e) {
|
|
82
|
+
console.error(e.message);
|
|
83
|
+
throw new WebAuthnAuthenticationError(`Could not start authentication: ${e.message}`);
|
|
84
|
+
}
|
|
85
|
+
return options;
|
|
86
|
+
}
|
|
87
|
+
async authenticate() {
|
|
88
|
+
const authOptions = await this.authenticationOptions();
|
|
89
|
+
const {
|
|
90
|
+
startAuthentication
|
|
91
|
+
} = await import('@simplewebauthn/browser');
|
|
92
|
+
try {
|
|
93
|
+
const browserResponse = await startAuthentication(authOptions);
|
|
94
|
+
const xhr = new XMLHttpRequest();
|
|
95
|
+
xhr.withCredentials = true;
|
|
96
|
+
xhr.open('POST', this.getAuthApiUrl(), false);
|
|
97
|
+
xhr.setRequestHeader('content-type', 'application/json');
|
|
98
|
+
xhr.send((0, _stringify.default)({
|
|
99
|
+
method: 'webAuthnAuthenticate',
|
|
100
|
+
...browserResponse
|
|
101
|
+
}));
|
|
102
|
+
const options = JSON.parse(xhr.responseText);
|
|
103
|
+
if (xhr.status !== 200) {
|
|
104
|
+
throw new WebAuthnAuthenticationError(`Could not complete authentication: ${options.error}`);
|
|
105
|
+
}
|
|
106
|
+
} catch (e) {
|
|
107
|
+
if (e.message.match(/No available authenticator recognized any of the allowed credentials/)) {
|
|
108
|
+
throw new WebAuthnNoAuthenticatorError();
|
|
109
|
+
} else {
|
|
110
|
+
throw new WebAuthnAuthenticationError(`Error while authenticating: ${e.message}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
registrationOptions() {
|
|
116
|
+
let options;
|
|
117
|
+
try {
|
|
118
|
+
const xhr = new XMLHttpRequest();
|
|
119
|
+
xhr.withCredentials = true;
|
|
120
|
+
xhr.open('GET', `${this.getAuthApiUrl()}?method=webAuthnRegOptions`, false);
|
|
121
|
+
xhr.setRequestHeader('content-type', 'application/json');
|
|
122
|
+
xhr.send(null);
|
|
123
|
+
options = JSON.parse(xhr.responseText);
|
|
124
|
+
if (xhr.status !== 200) {
|
|
125
|
+
throw new WebAuthnRegistrationError(`Could not start registration: ${options.error}`);
|
|
126
|
+
}
|
|
127
|
+
} catch (e) {
|
|
128
|
+
console.error(e);
|
|
129
|
+
throw new WebAuthnRegistrationError(`Could not start registration: ${e.message}`);
|
|
130
|
+
}
|
|
131
|
+
return options;
|
|
132
|
+
}
|
|
133
|
+
async register() {
|
|
134
|
+
const options = await this.registrationOptions();
|
|
135
|
+
let regResponse;
|
|
136
|
+
const {
|
|
137
|
+
startRegistration
|
|
138
|
+
} = await import('@simplewebauthn/browser');
|
|
139
|
+
try {
|
|
140
|
+
regResponse = await startRegistration(options);
|
|
141
|
+
} catch (e) {
|
|
142
|
+
if (e.name === 'InvalidStateError') {
|
|
143
|
+
throw new WebAuthnAlreadyRegisteredError();
|
|
144
|
+
} else {
|
|
145
|
+
throw new WebAuthnRegistrationError(`Error while registering: ${e.message}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
const xhr = new XMLHttpRequest();
|
|
150
|
+
xhr.withCredentials = true;
|
|
151
|
+
xhr.open('POST', this.getAuthApiUrl(), false);
|
|
152
|
+
xhr.setRequestHeader('content-type', 'application/json');
|
|
153
|
+
xhr.send((0, _stringify.default)({
|
|
154
|
+
method: 'webAuthnRegister',
|
|
155
|
+
...regResponse
|
|
156
|
+
}));
|
|
157
|
+
const options = JSON.parse(xhr.responseText);
|
|
158
|
+
if (xhr.status !== 200) {
|
|
159
|
+
throw new WebAuthnRegistrationError(`Could not complete registration: ${options.error}`);
|
|
160
|
+
}
|
|
161
|
+
} catch (e) {
|
|
162
|
+
throw new WebAuthnRegistrationError(`Error while registering: ${e.message}`);
|
|
163
|
+
}
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
exports.default = WebAuthnClient;
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cedarjs/auth-dbauth-web",
|
|
3
|
+
"version": "0.0.4",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/cedarjs/cedar.git",
|
|
7
|
+
"directory": "packages/auth-providers/dbAuth/web"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"webAuthn"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "yarn build:js && yarn build:types",
|
|
18
|
+
"build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\" --copy-files --no-copy-ignored",
|
|
19
|
+
"build:pack": "yarn pack -o cedar-auth-dbauth-web.tgz",
|
|
20
|
+
"build:types": "tsc --build --verbose",
|
|
21
|
+
"build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx,template\" --ignore dist --exec \"yarn build\"",
|
|
22
|
+
"prepublishOnly": "NODE_ENV=production yarn build",
|
|
23
|
+
"test": "yarn vitest run src",
|
|
24
|
+
"test:watch": "yarn test --watch"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@babel/runtime-corejs3": "7.26.10",
|
|
28
|
+
"@cedarjs/auth": "0.0.4",
|
|
29
|
+
"@simplewebauthn/browser": "7.4.0",
|
|
30
|
+
"core-js": "3.42.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@babel/cli": "7.26.4",
|
|
34
|
+
"@babel/core": "^7.26.10",
|
|
35
|
+
"@simplewebauthn/typescript-types": "7.4.0",
|
|
36
|
+
"@types/react": "^18.2.55",
|
|
37
|
+
"react": "18.3.1",
|
|
38
|
+
"typescript": "5.6.2",
|
|
39
|
+
"vitest": "2.1.9"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"gitHead": "5b4f77f985bd86ee31ee7338312627accf0cb85b"
|
|
45
|
+
}
|