ra-auth-auth0 1.0.0
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/Readme.md +100 -0
- package/esm/authProvider.d.ts +57 -0
- package/esm/authProvider.d.ts.map +1 -0
- package/esm/authProvider.js +222 -0
- package/esm/httpClient.d.ts +20 -0
- package/esm/httpClient.d.ts.map +1 -0
- package/esm/httpClient.js +81 -0
- package/esm/index.d.ts +3 -0
- package/esm/index.d.ts.map +1 -0
- package/esm/index.js +2 -0
- package/lib/authProvider.d.ts +57 -0
- package/lib/authProvider.d.ts.map +1 -0
- package/lib/authProvider.js +226 -0
- package/lib/httpClient.d.ts +20 -0
- package/lib/httpClient.d.ts.map +1 -0
- package/lib/httpClient.js +86 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +18 -0
- package/package.json +69 -0
- package/src/authProvider.ts +149 -0
- package/src/httpClient.ts +38 -0
- package/src/index.ts +2 -0
package/Readme.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# ra-auth-auth0
|
|
2
|
+
|
|
3
|
+
An auth provider for [react-admin](https://github.com/marmelab/react-admin) which handles authentication via a [Auth0](https://auth0.com) instance.
|
|
4
|
+
|
|
5
|
+
This package provides:
|
|
6
|
+
|
|
7
|
+
- An `Auth0AuthProvider` for react-admin
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
yarn add ra-auth-auth0
|
|
13
|
+
# or
|
|
14
|
+
npm install --save ra-auth-auth0
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Example usage
|
|
18
|
+
|
|
19
|
+
```jsx
|
|
20
|
+
// in src/App.tsx
|
|
21
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
22
|
+
import { Admin, Resource } from 'react-admin';
|
|
23
|
+
import { Auth0AuthProvider } from 'ra-auth-auth0';
|
|
24
|
+
import { Auth0Client } from '@auth0/auth0-spa-js';
|
|
25
|
+
import dataProvider from './dataProvider';
|
|
26
|
+
import posts from './posts';
|
|
27
|
+
|
|
28
|
+
const auth0 = new Auth0Client({
|
|
29
|
+
domain: import.meta.env.VITE_AUTH0_DOMAIN,
|
|
30
|
+
clientId: import.meta.env.VITE_AUTH0_CLIENT_ID,
|
|
31
|
+
cacheLocation: 'localstorage',
|
|
32
|
+
authorizationParams: {
|
|
33
|
+
audience: import.meta.env.VITE_AUTH0_AUDIENCE,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const authProvider = Auth0AuthProvider(auth0, {
|
|
38
|
+
loginRedirectUri: import.meta.env.VITE_LOGIN_REDIRECT_URL,
|
|
39
|
+
logoutRedirectUri: import.meta.env.VITE_LOGOUT_REDIRECT_URL,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const App = () => {
|
|
43
|
+
return (
|
|
44
|
+
<Admin
|
|
45
|
+
authProvider={authProvider}
|
|
46
|
+
dataProvider={dataProvider}
|
|
47
|
+
>
|
|
48
|
+
<Resource name="posts" {...posts} />
|
|
49
|
+
</Admin>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
export default App;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## `Auth0AuthProvider` Parameters
|
|
56
|
+
|
|
57
|
+
- `loginRedirectUri` - _optional_ - URI used to override the redirect URI after successful login. Defaults to react-admin `/auth-callback` route.
|
|
58
|
+
- `logoutRedirectUri` - _optional_ - URI used to override the redirect URI after successful logout. Defaults to the current url.
|
|
59
|
+
- `redirectOnCheckAuth` - _optional_ - If set to `true` (the default), redirect users to the Auth0 login page inside the [`checkAuth`](https://marmelab.com/react-admin/AuthProviderWriting.html#checkauth) method. Set it to `false` if you want users to go through a [custom login page](https://marmelab.com/react-admin/Authentication.html#customizing-the-login-component) first.
|
|
60
|
+
|
|
61
|
+
## Passing The Auth0 Token To Your Backend
|
|
62
|
+
|
|
63
|
+
If you want to pass the Auth0 authentication token to your backend, you can use the [`httpClient`](https://marmelab.com/react-admin/DataProviders.html#adding-custom-headers) exported by this package. For instance, here's how to use it with the [`ra-data-json-server` dataProvider](https://github.com/marmelab/react-admin/tree/master/packages/ra-data-json-server):
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
import jsonServerProvider from 'ra-data-json-server';
|
|
67
|
+
import { Auth0Client } from './auth0';
|
|
68
|
+
const dataProvider = jsonServerProvider('http://localhost:3000', httpClient(auth0));
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Handling Permissions
|
|
72
|
+
|
|
73
|
+
In order to get the users roles directly from Auth0, you have to configure it so that it includes them in the authentication token. This is done by [adding an Action to the login flow](https://auth0.com/docs/manage-users/access-control/sample-use-cases-actions-with-authorization#add-user-roles-to-tokens).
|
|
74
|
+
|
|
75
|
+
The `authProvider` exported by this package will look for a claim that has a name with the term `role` in it and return its value. To change this behavior, override the `getPermissions` method:
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
import { Auth0AuthProvider } from 'ra-auth-auth0';
|
|
79
|
+
import { Auth0Client } from './auth0';
|
|
80
|
+
|
|
81
|
+
const authProvider = {
|
|
82
|
+
...Auth0AuthProvider(Auth0Client),
|
|
83
|
+
async getPermissions() {
|
|
84
|
+
if (!(await client.isAuthenticated())) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const claims = await client.getIdTokenClaims();
|
|
89
|
+
return claims['https://my-app.example.com/roles'];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Demo
|
|
95
|
+
|
|
96
|
+
You can find a working demo, along with the source code, in this project's repository: https://github.com/marmelab/ra-auth-auth0
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
This auth provider is licensed under the MIT License and sponsored by [marmelab](https://marmelab.com).
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { AuthProvider } from 'react-admin';
|
|
2
|
+
import { Auth0Client } from '@auth0/auth0-spa-js';
|
|
3
|
+
/**
|
|
4
|
+
* An authProvider which handles authentication via the Auth0 instance.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* import React from 'react';
|
|
9
|
+
* import { Admin, Resource } from 'react-admin';
|
|
10
|
+
* import { Auth0AuthProvider } from 'ra-auth-auth0';
|
|
11
|
+
* import { Auth0Client } from '@auth0/auth0-spa-js';
|
|
12
|
+
* import dataProvider from './dataProvider';
|
|
13
|
+
* import posts from './posts';
|
|
14
|
+
*
|
|
15
|
+
* const clientAuth0 = new Auth0Client({
|
|
16
|
+
* domain: 'your-domain.auth0.com',
|
|
17
|
+
* clientId: 'your-client-id',
|
|
18
|
+
* cacheLocation: 'localstorage',
|
|
19
|
+
* // optional
|
|
20
|
+
* authorizationParams: {
|
|
21
|
+
* audience: 'https://your-domain.auth0.com/api/v2/',
|
|
22
|
+
* },
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* const authProvider = Auth0AuthProvider(clientAuth0, {
|
|
26
|
+
* loginRedirectUri: 'http://localhost:3000/auth-callback',
|
|
27
|
+
* logoutRedirectUri: 'http://localhost:3000',
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* const App = () => {
|
|
31
|
+
* return (
|
|
32
|
+
* <Admin
|
|
33
|
+
* authProvider={authProvider}
|
|
34
|
+
* dataProvider={dataProvider}
|
|
35
|
+
* title="Example Admin"
|
|
36
|
+
* >
|
|
37
|
+
* <Resource name="posts" {...posts} />
|
|
38
|
+
* </Admin>
|
|
39
|
+
* );
|
|
40
|
+
* };
|
|
41
|
+
* export default App;
|
|
42
|
+
*
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @param client the Auth0 client
|
|
46
|
+
* @param options The authProvider options
|
|
47
|
+
* @param options.loginRedirectUri The URI to which users should be redirected after they signed in. Must be whitelisted in the Auth0 application settings.
|
|
48
|
+
* @param options.logoutRedirectUri The URI to which users should be redirected after they signed out. Must be whitelisted in the Auth0 application settings.
|
|
49
|
+
* @param options.redirectOnCheckAuth If true, the user will be redirected to Auth0 when checkAuth fails. Defaults to true.
|
|
50
|
+
* @returns an authProvider ready to be used by React-Admin.
|
|
51
|
+
*/
|
|
52
|
+
export declare const Auth0AuthProvider: (client: Auth0Client, { loginRedirectUri, logoutRedirectUri, redirectOnCheckAuth, }?: {
|
|
53
|
+
loginRedirectUri?: string;
|
|
54
|
+
logoutRedirectUri?: string;
|
|
55
|
+
redirectOnCheckAuth?: boolean;
|
|
56
|
+
}) => AuthProvider;
|
|
57
|
+
//# sourceMappingURL=authProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authProvider.d.ts","sourceRoot":"","sources":["../src/authProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA8B,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,eAAO,MAAM,iBAAiB,WAClB,WAAW;uBAMI,MAAM;wBACL,MAAM;0BACJ,OAAO;MAIlC,YAmFD,CAAC"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
11
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
12
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
13
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
14
|
+
function step(op) {
|
|
15
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
16
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
17
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
18
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
19
|
+
switch (op[0]) {
|
|
20
|
+
case 0: case 1: t = op; break;
|
|
21
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
22
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
23
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
24
|
+
default:
|
|
25
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
26
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
27
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
28
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
29
|
+
if (t[2]) _.ops.pop();
|
|
30
|
+
_.trys.pop(); continue;
|
|
31
|
+
}
|
|
32
|
+
op = body.call(thisArg, _);
|
|
33
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
34
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
import { PreviousLocationStorageKey } from 'react-admin';
|
|
38
|
+
/**
|
|
39
|
+
* An authProvider which handles authentication via the Auth0 instance.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```tsx
|
|
43
|
+
* import React from 'react';
|
|
44
|
+
* import { Admin, Resource } from 'react-admin';
|
|
45
|
+
* import { Auth0AuthProvider } from 'ra-auth-auth0';
|
|
46
|
+
* import { Auth0Client } from '@auth0/auth0-spa-js';
|
|
47
|
+
* import dataProvider from './dataProvider';
|
|
48
|
+
* import posts from './posts';
|
|
49
|
+
*
|
|
50
|
+
* const clientAuth0 = new Auth0Client({
|
|
51
|
+
* domain: 'your-domain.auth0.com',
|
|
52
|
+
* clientId: 'your-client-id',
|
|
53
|
+
* cacheLocation: 'localstorage',
|
|
54
|
+
* // optional
|
|
55
|
+
* authorizationParams: {
|
|
56
|
+
* audience: 'https://your-domain.auth0.com/api/v2/',
|
|
57
|
+
* },
|
|
58
|
+
* });
|
|
59
|
+
*
|
|
60
|
+
* const authProvider = Auth0AuthProvider(clientAuth0, {
|
|
61
|
+
* loginRedirectUri: 'http://localhost:3000/auth-callback',
|
|
62
|
+
* logoutRedirectUri: 'http://localhost:3000',
|
|
63
|
+
* });
|
|
64
|
+
*
|
|
65
|
+
* const App = () => {
|
|
66
|
+
* return (
|
|
67
|
+
* <Admin
|
|
68
|
+
* authProvider={authProvider}
|
|
69
|
+
* dataProvider={dataProvider}
|
|
70
|
+
* title="Example Admin"
|
|
71
|
+
* >
|
|
72
|
+
* <Resource name="posts" {...posts} />
|
|
73
|
+
* </Admin>
|
|
74
|
+
* );
|
|
75
|
+
* };
|
|
76
|
+
* export default App;
|
|
77
|
+
*
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* @param client the Auth0 client
|
|
81
|
+
* @param options The authProvider options
|
|
82
|
+
* @param options.loginRedirectUri The URI to which users should be redirected after they signed in. Must be whitelisted in the Auth0 application settings.
|
|
83
|
+
* @param options.logoutRedirectUri The URI to which users should be redirected after they signed out. Must be whitelisted in the Auth0 application settings.
|
|
84
|
+
* @param options.redirectOnCheckAuth If true, the user will be redirected to Auth0 when checkAuth fails. Defaults to true.
|
|
85
|
+
* @returns an authProvider ready to be used by React-Admin.
|
|
86
|
+
*/
|
|
87
|
+
export var Auth0AuthProvider = function (client, _a) {
|
|
88
|
+
var _b = _a === void 0 ? {
|
|
89
|
+
redirectOnCheckAuth: true,
|
|
90
|
+
} : _a, loginRedirectUri = _b.loginRedirectUri, logoutRedirectUri = _b.logoutRedirectUri, _c = _b.redirectOnCheckAuth, redirectOnCheckAuth = _c === void 0 ? true : _c;
|
|
91
|
+
return ({
|
|
92
|
+
// Used when the redirection to Auth0 is done from a custom login page
|
|
93
|
+
login: function () {
|
|
94
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
95
|
+
return __generator(this, function (_a) {
|
|
96
|
+
client.loginWithRedirect({
|
|
97
|
+
authorizationParams: {
|
|
98
|
+
redirect_uri: loginRedirectUri !== null && loginRedirectUri !== void 0 ? loginRedirectUri : "".concat(window.location.origin, "/auth-callback"),
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
return [2 /*return*/];
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
// called when the user clicks on the logout button
|
|
106
|
+
logout: function () {
|
|
107
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
108
|
+
return __generator(this, function (_a) {
|
|
109
|
+
return [2 /*return*/, client.logout({
|
|
110
|
+
logoutParams: {
|
|
111
|
+
returnTo: logoutRedirectUri || window.location.origin,
|
|
112
|
+
},
|
|
113
|
+
})];
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
// called when the API returns an error
|
|
118
|
+
checkError: function (_a) {
|
|
119
|
+
var status = _a.status;
|
|
120
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
121
|
+
return __generator(this, function (_b) {
|
|
122
|
+
if (status === 401 || status === 403) {
|
|
123
|
+
throw new Error('Unauthorized');
|
|
124
|
+
}
|
|
125
|
+
return [2 /*return*/];
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
// called when the user navigates to a new location, to check for authentication
|
|
130
|
+
checkAuth: function () {
|
|
131
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
132
|
+
var isAuthenticated;
|
|
133
|
+
return __generator(this, function (_a) {
|
|
134
|
+
switch (_a.label) {
|
|
135
|
+
case 0: return [4 /*yield*/, client.isAuthenticated()];
|
|
136
|
+
case 1:
|
|
137
|
+
isAuthenticated = _a.sent();
|
|
138
|
+
if (isAuthenticated) {
|
|
139
|
+
return [2 /*return*/];
|
|
140
|
+
}
|
|
141
|
+
if (redirectOnCheckAuth) {
|
|
142
|
+
localStorage.setItem(PreviousLocationStorageKey, window.location.href);
|
|
143
|
+
client.loginWithRedirect({
|
|
144
|
+
authorizationParams: {
|
|
145
|
+
redirect_uri: loginRedirectUri !== null && loginRedirectUri !== void 0 ? loginRedirectUri : "".concat(window.location.origin, "/auth-callback"),
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return [2 /*return*/];
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
},
|
|
154
|
+
// called when the user navigates to a new location, to check for permissions / roles
|
|
155
|
+
getPermissions: function () {
|
|
156
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
157
|
+
var claims, roleProperty;
|
|
158
|
+
return __generator(this, function (_a) {
|
|
159
|
+
switch (_a.label) {
|
|
160
|
+
case 0: return [4 /*yield*/, client.isAuthenticated()];
|
|
161
|
+
case 1:
|
|
162
|
+
if (!(_a.sent())) {
|
|
163
|
+
return [2 /*return*/];
|
|
164
|
+
}
|
|
165
|
+
return [4 /*yield*/, client.getIdTokenClaims()];
|
|
166
|
+
case 2:
|
|
167
|
+
claims = _a.sent();
|
|
168
|
+
roleProperty = Object.keys(claims).find(function (key) {
|
|
169
|
+
return key.includes('role');
|
|
170
|
+
});
|
|
171
|
+
return [2 /*return*/, claims[roleProperty]];
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
},
|
|
176
|
+
getIdentity: function () {
|
|
177
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
178
|
+
var user;
|
|
179
|
+
return __generator(this, function (_a) {
|
|
180
|
+
switch (_a.label) {
|
|
181
|
+
case 0: return [4 /*yield*/, client.isAuthenticated()];
|
|
182
|
+
case 1:
|
|
183
|
+
if (!_a.sent()) return [3 /*break*/, 3];
|
|
184
|
+
return [4 /*yield*/, client.getUser()];
|
|
185
|
+
case 2:
|
|
186
|
+
user = _a.sent();
|
|
187
|
+
return [2 /*return*/, {
|
|
188
|
+
id: user.email,
|
|
189
|
+
fullName: user.name,
|
|
190
|
+
avatar: user.picture,
|
|
191
|
+
}];
|
|
192
|
+
case 3: throw new Error('Failed to get identity.');
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
},
|
|
197
|
+
handleCallback: function () {
|
|
198
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
199
|
+
var query, error_1;
|
|
200
|
+
return __generator(this, function (_a) {
|
|
201
|
+
switch (_a.label) {
|
|
202
|
+
case 0:
|
|
203
|
+
query = window.location.search;
|
|
204
|
+
if (!(query.includes('code=') && query.includes('state='))) return [3 /*break*/, 4];
|
|
205
|
+
_a.label = 1;
|
|
206
|
+
case 1:
|
|
207
|
+
_a.trys.push([1, 3, , 4]);
|
|
208
|
+
return [4 /*yield*/, client.handleRedirectCallback()];
|
|
209
|
+
case 2:
|
|
210
|
+
_a.sent();
|
|
211
|
+
return [2 /*return*/];
|
|
212
|
+
case 3:
|
|
213
|
+
error_1 = _a.sent();
|
|
214
|
+
console.log('error', error_1);
|
|
215
|
+
throw error_1;
|
|
216
|
+
case 4: throw new Error('Failed to handle login callback.');
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { fetchUtils } from 'react-admin';
|
|
2
|
+
/**
|
|
3
|
+
* An httpClient that adds authentication headers needed by Auth0 in all requests.
|
|
4
|
+
* @param auth0Client the Auth0 client.
|
|
5
|
+
* @returns a function with the same definition as `httpClient` that adds an `Authorization` header containing the Auth0 token.
|
|
6
|
+
*/
|
|
7
|
+
export declare const httpClient: (auth0Client: any) => (url: any, options: fetchUtils.Options | undefined) => Promise<{
|
|
8
|
+
status: number;
|
|
9
|
+
headers: Headers;
|
|
10
|
+
body: string;
|
|
11
|
+
json: any;
|
|
12
|
+
}>;
|
|
13
|
+
/**
|
|
14
|
+
* Return the headers needed by Auth0.
|
|
15
|
+
* @param token the Auth0 token
|
|
16
|
+
* @param options the fetch options (so that we do not override other headers)
|
|
17
|
+
* @returns the headers needed by Auth0
|
|
18
|
+
*/
|
|
19
|
+
export declare const getAuth0Headers: (token: string | null, options: fetchUtils.Options | undefined) => Headers;
|
|
20
|
+
//# sourceMappingURL=httpClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"httpClient.d.ts","sourceRoot":"","sources":["../src/httpClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;GAIG;AACH,eAAO,MAAM,UAAU,gBAAiB,GAAG,WAClC,GAAG,WACC,WAAW,OAAO,GAAG,SAAS;;;;;EAQ1C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,UACjB,MAAM,GAAG,IAAI,WACX,WAAW,OAAO,GAAG,SAAS,KACxC,OASF,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
|
2
|
+
__assign = Object.assign || function(t) {
|
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
+
s = arguments[i];
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
return __assign.apply(this, arguments);
|
|
11
|
+
};
|
|
12
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
13
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
14
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
15
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
16
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
17
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
18
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
22
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
23
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
24
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
25
|
+
function step(op) {
|
|
26
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
27
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
28
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
29
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
30
|
+
switch (op[0]) {
|
|
31
|
+
case 0: case 1: t = op; break;
|
|
32
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
33
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
34
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
35
|
+
default:
|
|
36
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
37
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
38
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
39
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
40
|
+
if (t[2]) _.ops.pop();
|
|
41
|
+
_.trys.pop(); continue;
|
|
42
|
+
}
|
|
43
|
+
op = body.call(thisArg, _);
|
|
44
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
45
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
import { fetchUtils } from 'react-admin';
|
|
49
|
+
/**
|
|
50
|
+
* An httpClient that adds authentication headers needed by Auth0 in all requests.
|
|
51
|
+
* @param auth0Client the Auth0 client.
|
|
52
|
+
* @returns a function with the same definition as `httpClient` that adds an `Authorization` header containing the Auth0 token.
|
|
53
|
+
*/
|
|
54
|
+
export var httpClient = function (auth0Client) { return function (url, options) { return __awaiter(void 0, void 0, void 0, function () {
|
|
55
|
+
var token, requestHeaders;
|
|
56
|
+
return __generator(this, function (_a) {
|
|
57
|
+
switch (_a.label) {
|
|
58
|
+
case 0: return [4 /*yield*/, auth0Client.getTokenSilently()];
|
|
59
|
+
case 1:
|
|
60
|
+
token = _a.sent();
|
|
61
|
+
requestHeaders = getAuth0Headers(token, options);
|
|
62
|
+
return [2 /*return*/, fetchUtils.fetchJson(url, __assign(__assign({}, options), { headers: requestHeaders }))];
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}); }; };
|
|
66
|
+
/**
|
|
67
|
+
* Return the headers needed by Auth0.
|
|
68
|
+
* @param token the Auth0 token
|
|
69
|
+
* @param options the fetch options (so that we do not override other headers)
|
|
70
|
+
* @returns the headers needed by Auth0
|
|
71
|
+
*/
|
|
72
|
+
export var getAuth0Headers = function (token, options) {
|
|
73
|
+
var headers = ((options && options.headers) ||
|
|
74
|
+
new Headers({
|
|
75
|
+
Accept: 'application/json',
|
|
76
|
+
}));
|
|
77
|
+
if (token) {
|
|
78
|
+
headers.set('Authorization', "Bearer ".concat(token));
|
|
79
|
+
}
|
|
80
|
+
return headers;
|
|
81
|
+
};
|
package/esm/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC"}
|
package/esm/index.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { AuthProvider } from 'react-admin';
|
|
2
|
+
import { Auth0Client } from '@auth0/auth0-spa-js';
|
|
3
|
+
/**
|
|
4
|
+
* An authProvider which handles authentication via the Auth0 instance.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* import React from 'react';
|
|
9
|
+
* import { Admin, Resource } from 'react-admin';
|
|
10
|
+
* import { Auth0AuthProvider } from 'ra-auth-auth0';
|
|
11
|
+
* import { Auth0Client } from '@auth0/auth0-spa-js';
|
|
12
|
+
* import dataProvider from './dataProvider';
|
|
13
|
+
* import posts from './posts';
|
|
14
|
+
*
|
|
15
|
+
* const clientAuth0 = new Auth0Client({
|
|
16
|
+
* domain: 'your-domain.auth0.com',
|
|
17
|
+
* clientId: 'your-client-id',
|
|
18
|
+
* cacheLocation: 'localstorage',
|
|
19
|
+
* // optional
|
|
20
|
+
* authorizationParams: {
|
|
21
|
+
* audience: 'https://your-domain.auth0.com/api/v2/',
|
|
22
|
+
* },
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* const authProvider = Auth0AuthProvider(clientAuth0, {
|
|
26
|
+
* loginRedirectUri: 'http://localhost:3000/auth-callback',
|
|
27
|
+
* logoutRedirectUri: 'http://localhost:3000',
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* const App = () => {
|
|
31
|
+
* return (
|
|
32
|
+
* <Admin
|
|
33
|
+
* authProvider={authProvider}
|
|
34
|
+
* dataProvider={dataProvider}
|
|
35
|
+
* title="Example Admin"
|
|
36
|
+
* >
|
|
37
|
+
* <Resource name="posts" {...posts} />
|
|
38
|
+
* </Admin>
|
|
39
|
+
* );
|
|
40
|
+
* };
|
|
41
|
+
* export default App;
|
|
42
|
+
*
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @param client the Auth0 client
|
|
46
|
+
* @param options The authProvider options
|
|
47
|
+
* @param options.loginRedirectUri The URI to which users should be redirected after they signed in. Must be whitelisted in the Auth0 application settings.
|
|
48
|
+
* @param options.logoutRedirectUri The URI to which users should be redirected after they signed out. Must be whitelisted in the Auth0 application settings.
|
|
49
|
+
* @param options.redirectOnCheckAuth If true, the user will be redirected to Auth0 when checkAuth fails. Defaults to true.
|
|
50
|
+
* @returns an authProvider ready to be used by React-Admin.
|
|
51
|
+
*/
|
|
52
|
+
export declare const Auth0AuthProvider: (client: Auth0Client, { loginRedirectUri, logoutRedirectUri, redirectOnCheckAuth, }?: {
|
|
53
|
+
loginRedirectUri?: string;
|
|
54
|
+
logoutRedirectUri?: string;
|
|
55
|
+
redirectOnCheckAuth?: boolean;
|
|
56
|
+
}) => AuthProvider;
|
|
57
|
+
//# sourceMappingURL=authProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authProvider.d.ts","sourceRoot":"","sources":["../src/authProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA8B,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,eAAO,MAAM,iBAAiB,WAClB,WAAW;uBAMI,MAAM;wBACL,MAAM;0BACJ,OAAO;MAIlC,YAmFD,CAAC"}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
13
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.Auth0AuthProvider = void 0;
|
|
40
|
+
var react_admin_1 = require("react-admin");
|
|
41
|
+
/**
|
|
42
|
+
* An authProvider which handles authentication via the Auth0 instance.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```tsx
|
|
46
|
+
* import React from 'react';
|
|
47
|
+
* import { Admin, Resource } from 'react-admin';
|
|
48
|
+
* import { Auth0AuthProvider } from 'ra-auth-auth0';
|
|
49
|
+
* import { Auth0Client } from '@auth0/auth0-spa-js';
|
|
50
|
+
* import dataProvider from './dataProvider';
|
|
51
|
+
* import posts from './posts';
|
|
52
|
+
*
|
|
53
|
+
* const clientAuth0 = new Auth0Client({
|
|
54
|
+
* domain: 'your-domain.auth0.com',
|
|
55
|
+
* clientId: 'your-client-id',
|
|
56
|
+
* cacheLocation: 'localstorage',
|
|
57
|
+
* // optional
|
|
58
|
+
* authorizationParams: {
|
|
59
|
+
* audience: 'https://your-domain.auth0.com/api/v2/',
|
|
60
|
+
* },
|
|
61
|
+
* });
|
|
62
|
+
*
|
|
63
|
+
* const authProvider = Auth0AuthProvider(clientAuth0, {
|
|
64
|
+
* loginRedirectUri: 'http://localhost:3000/auth-callback',
|
|
65
|
+
* logoutRedirectUri: 'http://localhost:3000',
|
|
66
|
+
* });
|
|
67
|
+
*
|
|
68
|
+
* const App = () => {
|
|
69
|
+
* return (
|
|
70
|
+
* <Admin
|
|
71
|
+
* authProvider={authProvider}
|
|
72
|
+
* dataProvider={dataProvider}
|
|
73
|
+
* title="Example Admin"
|
|
74
|
+
* >
|
|
75
|
+
* <Resource name="posts" {...posts} />
|
|
76
|
+
* </Admin>
|
|
77
|
+
* );
|
|
78
|
+
* };
|
|
79
|
+
* export default App;
|
|
80
|
+
*
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @param client the Auth0 client
|
|
84
|
+
* @param options The authProvider options
|
|
85
|
+
* @param options.loginRedirectUri The URI to which users should be redirected after they signed in. Must be whitelisted in the Auth0 application settings.
|
|
86
|
+
* @param options.logoutRedirectUri The URI to which users should be redirected after they signed out. Must be whitelisted in the Auth0 application settings.
|
|
87
|
+
* @param options.redirectOnCheckAuth If true, the user will be redirected to Auth0 when checkAuth fails. Defaults to true.
|
|
88
|
+
* @returns an authProvider ready to be used by React-Admin.
|
|
89
|
+
*/
|
|
90
|
+
var Auth0AuthProvider = function (client, _a) {
|
|
91
|
+
var _b = _a === void 0 ? {
|
|
92
|
+
redirectOnCheckAuth: true,
|
|
93
|
+
} : _a, loginRedirectUri = _b.loginRedirectUri, logoutRedirectUri = _b.logoutRedirectUri, _c = _b.redirectOnCheckAuth, redirectOnCheckAuth = _c === void 0 ? true : _c;
|
|
94
|
+
return ({
|
|
95
|
+
// Used when the redirection to Auth0 is done from a custom login page
|
|
96
|
+
login: function () {
|
|
97
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
98
|
+
return __generator(this, function (_a) {
|
|
99
|
+
client.loginWithRedirect({
|
|
100
|
+
authorizationParams: {
|
|
101
|
+
redirect_uri: loginRedirectUri !== null && loginRedirectUri !== void 0 ? loginRedirectUri : "".concat(window.location.origin, "/auth-callback"),
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
return [2 /*return*/];
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
// called when the user clicks on the logout button
|
|
109
|
+
logout: function () {
|
|
110
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
111
|
+
return __generator(this, function (_a) {
|
|
112
|
+
return [2 /*return*/, client.logout({
|
|
113
|
+
logoutParams: {
|
|
114
|
+
returnTo: logoutRedirectUri || window.location.origin,
|
|
115
|
+
},
|
|
116
|
+
})];
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
// called when the API returns an error
|
|
121
|
+
checkError: function (_a) {
|
|
122
|
+
var status = _a.status;
|
|
123
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
124
|
+
return __generator(this, function (_b) {
|
|
125
|
+
if (status === 401 || status === 403) {
|
|
126
|
+
throw new Error('Unauthorized');
|
|
127
|
+
}
|
|
128
|
+
return [2 /*return*/];
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
// called when the user navigates to a new location, to check for authentication
|
|
133
|
+
checkAuth: function () {
|
|
134
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
135
|
+
var isAuthenticated;
|
|
136
|
+
return __generator(this, function (_a) {
|
|
137
|
+
switch (_a.label) {
|
|
138
|
+
case 0: return [4 /*yield*/, client.isAuthenticated()];
|
|
139
|
+
case 1:
|
|
140
|
+
isAuthenticated = _a.sent();
|
|
141
|
+
if (isAuthenticated) {
|
|
142
|
+
return [2 /*return*/];
|
|
143
|
+
}
|
|
144
|
+
if (redirectOnCheckAuth) {
|
|
145
|
+
localStorage.setItem(react_admin_1.PreviousLocationStorageKey, window.location.href);
|
|
146
|
+
client.loginWithRedirect({
|
|
147
|
+
authorizationParams: {
|
|
148
|
+
redirect_uri: loginRedirectUri !== null && loginRedirectUri !== void 0 ? loginRedirectUri : "".concat(window.location.origin, "/auth-callback"),
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return [2 /*return*/];
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
},
|
|
157
|
+
// called when the user navigates to a new location, to check for permissions / roles
|
|
158
|
+
getPermissions: function () {
|
|
159
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
160
|
+
var claims, roleProperty;
|
|
161
|
+
return __generator(this, function (_a) {
|
|
162
|
+
switch (_a.label) {
|
|
163
|
+
case 0: return [4 /*yield*/, client.isAuthenticated()];
|
|
164
|
+
case 1:
|
|
165
|
+
if (!(_a.sent())) {
|
|
166
|
+
return [2 /*return*/];
|
|
167
|
+
}
|
|
168
|
+
return [4 /*yield*/, client.getIdTokenClaims()];
|
|
169
|
+
case 2:
|
|
170
|
+
claims = _a.sent();
|
|
171
|
+
roleProperty = Object.keys(claims).find(function (key) {
|
|
172
|
+
return key.includes('role');
|
|
173
|
+
});
|
|
174
|
+
return [2 /*return*/, claims[roleProperty]];
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
},
|
|
179
|
+
getIdentity: function () {
|
|
180
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
181
|
+
var user;
|
|
182
|
+
return __generator(this, function (_a) {
|
|
183
|
+
switch (_a.label) {
|
|
184
|
+
case 0: return [4 /*yield*/, client.isAuthenticated()];
|
|
185
|
+
case 1:
|
|
186
|
+
if (!_a.sent()) return [3 /*break*/, 3];
|
|
187
|
+
return [4 /*yield*/, client.getUser()];
|
|
188
|
+
case 2:
|
|
189
|
+
user = _a.sent();
|
|
190
|
+
return [2 /*return*/, {
|
|
191
|
+
id: user.email,
|
|
192
|
+
fullName: user.name,
|
|
193
|
+
avatar: user.picture,
|
|
194
|
+
}];
|
|
195
|
+
case 3: throw new Error('Failed to get identity.');
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
},
|
|
200
|
+
handleCallback: function () {
|
|
201
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
202
|
+
var query, error_1;
|
|
203
|
+
return __generator(this, function (_a) {
|
|
204
|
+
switch (_a.label) {
|
|
205
|
+
case 0:
|
|
206
|
+
query = window.location.search;
|
|
207
|
+
if (!(query.includes('code=') && query.includes('state='))) return [3 /*break*/, 4];
|
|
208
|
+
_a.label = 1;
|
|
209
|
+
case 1:
|
|
210
|
+
_a.trys.push([1, 3, , 4]);
|
|
211
|
+
return [4 /*yield*/, client.handleRedirectCallback()];
|
|
212
|
+
case 2:
|
|
213
|
+
_a.sent();
|
|
214
|
+
return [2 /*return*/];
|
|
215
|
+
case 3:
|
|
216
|
+
error_1 = _a.sent();
|
|
217
|
+
console.log('error', error_1);
|
|
218
|
+
throw error_1;
|
|
219
|
+
case 4: throw new Error('Failed to handle login callback.');
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
exports.Auth0AuthProvider = Auth0AuthProvider;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { fetchUtils } from 'react-admin';
|
|
2
|
+
/**
|
|
3
|
+
* An httpClient that adds authentication headers needed by Auth0 in all requests.
|
|
4
|
+
* @param auth0Client the Auth0 client.
|
|
5
|
+
* @returns a function with the same definition as `httpClient` that adds an `Authorization` header containing the Auth0 token.
|
|
6
|
+
*/
|
|
7
|
+
export declare const httpClient: (auth0Client: any) => (url: any, options: fetchUtils.Options | undefined) => Promise<{
|
|
8
|
+
status: number;
|
|
9
|
+
headers: Headers;
|
|
10
|
+
body: string;
|
|
11
|
+
json: any;
|
|
12
|
+
}>;
|
|
13
|
+
/**
|
|
14
|
+
* Return the headers needed by Auth0.
|
|
15
|
+
* @param token the Auth0 token
|
|
16
|
+
* @param options the fetch options (so that we do not override other headers)
|
|
17
|
+
* @returns the headers needed by Auth0
|
|
18
|
+
*/
|
|
19
|
+
export declare const getAuth0Headers: (token: string | null, options: fetchUtils.Options | undefined) => Headers;
|
|
20
|
+
//# sourceMappingURL=httpClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"httpClient.d.ts","sourceRoot":"","sources":["../src/httpClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;GAIG;AACH,eAAO,MAAM,UAAU,gBAAiB,GAAG,WAClC,GAAG,WACC,WAAW,OAAO,GAAG,SAAS;;;;;EAQ1C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,UACjB,MAAM,GAAG,IAAI,WACX,WAAW,OAAO,GAAG,SAAS,KACxC,OASF,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
13
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
14
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
15
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
16
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
17
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
18
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
19
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
23
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
24
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
25
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
26
|
+
function step(op) {
|
|
27
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
28
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
29
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
30
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
31
|
+
switch (op[0]) {
|
|
32
|
+
case 0: case 1: t = op; break;
|
|
33
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
34
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
35
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
36
|
+
default:
|
|
37
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
38
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
39
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
40
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
41
|
+
if (t[2]) _.ops.pop();
|
|
42
|
+
_.trys.pop(); continue;
|
|
43
|
+
}
|
|
44
|
+
op = body.call(thisArg, _);
|
|
45
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
46
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
+
exports.getAuth0Headers = exports.httpClient = void 0;
|
|
51
|
+
var react_admin_1 = require("react-admin");
|
|
52
|
+
/**
|
|
53
|
+
* An httpClient that adds authentication headers needed by Auth0 in all requests.
|
|
54
|
+
* @param auth0Client the Auth0 client.
|
|
55
|
+
* @returns a function with the same definition as `httpClient` that adds an `Authorization` header containing the Auth0 token.
|
|
56
|
+
*/
|
|
57
|
+
var httpClient = function (auth0Client) { return function (url, options) { return __awaiter(void 0, void 0, void 0, function () {
|
|
58
|
+
var token, requestHeaders;
|
|
59
|
+
return __generator(this, function (_a) {
|
|
60
|
+
switch (_a.label) {
|
|
61
|
+
case 0: return [4 /*yield*/, auth0Client.getTokenSilently()];
|
|
62
|
+
case 1:
|
|
63
|
+
token = _a.sent();
|
|
64
|
+
requestHeaders = (0, exports.getAuth0Headers)(token, options);
|
|
65
|
+
return [2 /*return*/, react_admin_1.fetchUtils.fetchJson(url, __assign(__assign({}, options), { headers: requestHeaders }))];
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}); }; };
|
|
69
|
+
exports.httpClient = httpClient;
|
|
70
|
+
/**
|
|
71
|
+
* Return the headers needed by Auth0.
|
|
72
|
+
* @param token the Auth0 token
|
|
73
|
+
* @param options the fetch options (so that we do not override other headers)
|
|
74
|
+
* @returns the headers needed by Auth0
|
|
75
|
+
*/
|
|
76
|
+
var getAuth0Headers = function (token, options) {
|
|
77
|
+
var headers = ((options && options.headers) ||
|
|
78
|
+
new Headers({
|
|
79
|
+
Accept: 'application/json',
|
|
80
|
+
}));
|
|
81
|
+
if (token) {
|
|
82
|
+
headers.set('Authorization', "Bearer ".concat(token));
|
|
83
|
+
}
|
|
84
|
+
return headers;
|
|
85
|
+
};
|
|
86
|
+
exports.getAuth0Headers = getAuth0Headers;
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./authProvider"), exports);
|
|
18
|
+
__exportStar(require("./httpClient"), exports);
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ra-auth-auth0",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"repository": "git@github.com:marmelab/ra-auth-auth0.git",
|
|
5
|
+
"author": "Anthony Rimet <anthony@marmelab.com> (https://marmelab.com/)",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"files": [
|
|
8
|
+
"*.md",
|
|
9
|
+
"lib",
|
|
10
|
+
"esm",
|
|
11
|
+
"src"
|
|
12
|
+
],
|
|
13
|
+
"main": "lib/index",
|
|
14
|
+
"module": "esm/index.js",
|
|
15
|
+
"types": "esm/index.d.ts",
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"@auth0/auth0-spa-js": "^2.0.0",
|
|
19
|
+
"ra-core": "^4.7.0",
|
|
20
|
+
"react": "^16.9.0 || ^17.0.0 || ^18.0.0",
|
|
21
|
+
"react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"react-admin": "^4.7.0"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "yarn run build-cjs && yarn run build-esm",
|
|
28
|
+
"build-cjs": "rimraf ./lib && tsc",
|
|
29
|
+
"build-esm": "rimraf ./esm && tsc --outDir esm --module es2015",
|
|
30
|
+
"watch": "tsc --outDir esm --module es2015 --watch",
|
|
31
|
+
"lint": "eslint --fix ./src",
|
|
32
|
+
"test-unit": "jest"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/jest": "^29.1.0",
|
|
36
|
+
"jest": "^29.1.1",
|
|
37
|
+
"jest-environment-jsdom": "^29.1.1",
|
|
38
|
+
"react": "^18.2.0",
|
|
39
|
+
"react-dom": "^18.2.0",
|
|
40
|
+
"rimraf": "^3.0.2",
|
|
41
|
+
"ts-jest": "^29.0.3",
|
|
42
|
+
"typescript": "^4.8.4"
|
|
43
|
+
},
|
|
44
|
+
"jest": {
|
|
45
|
+
"preset": "ts-jest",
|
|
46
|
+
"testEnvironment": "jsdom",
|
|
47
|
+
"collectCoverageFrom": [
|
|
48
|
+
"**/*.js",
|
|
49
|
+
"**/*.ts",
|
|
50
|
+
"!__tests__/util.ts",
|
|
51
|
+
"!coverage/**",
|
|
52
|
+
"!**/node_modules/**"
|
|
53
|
+
],
|
|
54
|
+
"verbose": true,
|
|
55
|
+
"automock": false,
|
|
56
|
+
"clearMocks": true,
|
|
57
|
+
"testPathIgnorePatterns": [
|
|
58
|
+
"/node_modules/"
|
|
59
|
+
],
|
|
60
|
+
"moduleFileExtensions": [
|
|
61
|
+
"ts",
|
|
62
|
+
"tsx",
|
|
63
|
+
"js",
|
|
64
|
+
"jsx",
|
|
65
|
+
"json",
|
|
66
|
+
"node"
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { AuthProvider, PreviousLocationStorageKey } from 'react-admin';
|
|
2
|
+
import { Auth0Client } from '@auth0/auth0-spa-js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* An authProvider which handles authentication via the Auth0 instance.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import React from 'react';
|
|
10
|
+
* import { Admin, Resource } from 'react-admin';
|
|
11
|
+
* import { Auth0AuthProvider } from 'ra-auth-auth0';
|
|
12
|
+
* import { Auth0Client } from '@auth0/auth0-spa-js';
|
|
13
|
+
* import dataProvider from './dataProvider';
|
|
14
|
+
* import posts from './posts';
|
|
15
|
+
*
|
|
16
|
+
* const clientAuth0 = new Auth0Client({
|
|
17
|
+
* domain: 'your-domain.auth0.com',
|
|
18
|
+
* clientId: 'your-client-id',
|
|
19
|
+
* cacheLocation: 'localstorage',
|
|
20
|
+
* // optional
|
|
21
|
+
* authorizationParams: {
|
|
22
|
+
* audience: 'https://your-domain.auth0.com/api/v2/',
|
|
23
|
+
* },
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* const authProvider = Auth0AuthProvider(clientAuth0, {
|
|
27
|
+
* loginRedirectUri: 'http://localhost:3000/auth-callback',
|
|
28
|
+
* logoutRedirectUri: 'http://localhost:3000',
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* const App = () => {
|
|
32
|
+
* return (
|
|
33
|
+
* <Admin
|
|
34
|
+
* authProvider={authProvider}
|
|
35
|
+
* dataProvider={dataProvider}
|
|
36
|
+
* title="Example Admin"
|
|
37
|
+
* >
|
|
38
|
+
* <Resource name="posts" {...posts} />
|
|
39
|
+
* </Admin>
|
|
40
|
+
* );
|
|
41
|
+
* };
|
|
42
|
+
* export default App;
|
|
43
|
+
*
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @param client the Auth0 client
|
|
47
|
+
* @param options The authProvider options
|
|
48
|
+
* @param options.loginRedirectUri The URI to which users should be redirected after they signed in. Must be whitelisted in the Auth0 application settings.
|
|
49
|
+
* @param options.logoutRedirectUri The URI to which users should be redirected after they signed out. Must be whitelisted in the Auth0 application settings.
|
|
50
|
+
* @param options.redirectOnCheckAuth If true, the user will be redirected to Auth0 when checkAuth fails. Defaults to true.
|
|
51
|
+
* @returns an authProvider ready to be used by React-Admin.
|
|
52
|
+
*/
|
|
53
|
+
export const Auth0AuthProvider = (
|
|
54
|
+
client: Auth0Client,
|
|
55
|
+
{
|
|
56
|
+
loginRedirectUri,
|
|
57
|
+
logoutRedirectUri,
|
|
58
|
+
redirectOnCheckAuth = true,
|
|
59
|
+
}: {
|
|
60
|
+
loginRedirectUri?: string;
|
|
61
|
+
logoutRedirectUri?: string;
|
|
62
|
+
redirectOnCheckAuth?: boolean;
|
|
63
|
+
} = {
|
|
64
|
+
redirectOnCheckAuth: true,
|
|
65
|
+
}
|
|
66
|
+
): AuthProvider => ({
|
|
67
|
+
// Used when the redirection to Auth0 is done from a custom login page
|
|
68
|
+
async login() {
|
|
69
|
+
client.loginWithRedirect({
|
|
70
|
+
authorizationParams: {
|
|
71
|
+
redirect_uri:
|
|
72
|
+
loginRedirectUri ??
|
|
73
|
+
`${window.location.origin}/auth-callback`,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
// called when the user clicks on the logout button
|
|
78
|
+
async logout() {
|
|
79
|
+
return client.logout({
|
|
80
|
+
logoutParams: {
|
|
81
|
+
returnTo: logoutRedirectUri || window.location.origin,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
// called when the API returns an error
|
|
86
|
+
async checkError({ status }) {
|
|
87
|
+
if (status === 401 || status === 403) {
|
|
88
|
+
throw new Error('Unauthorized');
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
// called when the user navigates to a new location, to check for authentication
|
|
92
|
+
async checkAuth() {
|
|
93
|
+
const isAuthenticated = await client.isAuthenticated();
|
|
94
|
+
if (isAuthenticated) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (redirectOnCheckAuth) {
|
|
99
|
+
localStorage.setItem(
|
|
100
|
+
PreviousLocationStorageKey,
|
|
101
|
+
window.location.href
|
|
102
|
+
);
|
|
103
|
+
client.loginWithRedirect({
|
|
104
|
+
authorizationParams: {
|
|
105
|
+
redirect_uri:
|
|
106
|
+
loginRedirectUri ??
|
|
107
|
+
`${window.location.origin}/auth-callback`,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
// called when the user navigates to a new location, to check for permissions / roles
|
|
113
|
+
async getPermissions() {
|
|
114
|
+
if (!(await client.isAuthenticated())) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// If Auth0 instance contains rules for returning permissions, use them
|
|
119
|
+
const claims = await client.getIdTokenClaims();
|
|
120
|
+
const roleProperty = Object.keys(claims).find(key =>
|
|
121
|
+
key.includes('role')
|
|
122
|
+
);
|
|
123
|
+
return claims[roleProperty];
|
|
124
|
+
},
|
|
125
|
+
async getIdentity() {
|
|
126
|
+
if (await client.isAuthenticated()) {
|
|
127
|
+
const user = await client.getUser();
|
|
128
|
+
return {
|
|
129
|
+
id: user.email,
|
|
130
|
+
fullName: user.name,
|
|
131
|
+
avatar: user.picture,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
throw new Error('Failed to get identity.');
|
|
135
|
+
},
|
|
136
|
+
async handleCallback() {
|
|
137
|
+
const query = window.location.search;
|
|
138
|
+
if (query.includes('code=') && query.includes('state=')) {
|
|
139
|
+
try {
|
|
140
|
+
await client.handleRedirectCallback();
|
|
141
|
+
return;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.log('error', error);
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
throw new Error('Failed to handle login callback.');
|
|
148
|
+
},
|
|
149
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { fetchUtils } from 'react-admin';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* An httpClient that adds authentication headers needed by Auth0 in all requests.
|
|
5
|
+
* @param auth0Client the Auth0 client.
|
|
6
|
+
* @returns a function with the same definition as `httpClient` that adds an `Authorization` header containing the Auth0 token.
|
|
7
|
+
*/
|
|
8
|
+
export const httpClient = (auth0Client: any) => async (
|
|
9
|
+
url: any,
|
|
10
|
+
options: fetchUtils.Options | undefined
|
|
11
|
+
) => {
|
|
12
|
+
const token = await auth0Client.getTokenSilently();
|
|
13
|
+
const requestHeaders = getAuth0Headers(token, options);
|
|
14
|
+
return fetchUtils.fetchJson(url, {
|
|
15
|
+
...options,
|
|
16
|
+
headers: requestHeaders,
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Return the headers needed by Auth0.
|
|
22
|
+
* @param token the Auth0 token
|
|
23
|
+
* @param options the fetch options (so that we do not override other headers)
|
|
24
|
+
* @returns the headers needed by Auth0
|
|
25
|
+
*/
|
|
26
|
+
export const getAuth0Headers = (
|
|
27
|
+
token: string | null,
|
|
28
|
+
options: fetchUtils.Options | undefined
|
|
29
|
+
): Headers => {
|
|
30
|
+
const headers = ((options && options.headers) ||
|
|
31
|
+
new Headers({
|
|
32
|
+
Accept: 'application/json',
|
|
33
|
+
})) as Headers;
|
|
34
|
+
if (token) {
|
|
35
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
36
|
+
}
|
|
37
|
+
return headers;
|
|
38
|
+
};
|
package/src/index.ts
ADDED