@logto/react-router 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/LICENSE +21 -0
- package/README.md +144 -0
- package/lib/framework/get-cookie-header-from-request.d.ts +1 -0
- package/lib/framework/get-cookie-header-from-request.js +3 -0
- package/lib/framework/mocks.d.ts +25 -0
- package/lib/framework/mocks.js +50 -0
- package/lib/index.d.ts +30 -0
- package/lib/index.js +23 -0
- package/lib/infrastructure/logto/create-client.d.ts +6 -0
- package/lib/infrastructure/logto/create-client.js +11 -0
- package/lib/infrastructure/logto/create-client.spec.d.ts +1 -0
- package/lib/infrastructure/logto/create-client.spec.js +16 -0
- package/lib/infrastructure/logto/create-storage.d.ts +14 -0
- package/lib/infrastructure/logto/create-storage.js +28 -0
- package/lib/infrastructure/logto/create-storage.spec.d.ts +1 -0
- package/lib/infrastructure/logto/create-storage.spec.js +39 -0
- package/lib/infrastructure/logto/get-context.d.ts +11 -0
- package/lib/infrastructure/logto/get-context.js +10 -0
- package/lib/infrastructure/logto/handle-sign-in-callback.d.ts +14 -0
- package/lib/infrastructure/logto/handle-sign-in-callback.js +10 -0
- package/lib/infrastructure/logto/handle-sign-in.d.ts +15 -0
- package/lib/infrastructure/logto/handle-sign-in.js +28 -0
- package/lib/infrastructure/logto/handle-sign-out.d.ts +11 -0
- package/lib/infrastructure/logto/handle-sign-out.js +24 -0
- package/lib/infrastructure/logto/handle-sign-up.d.ts +15 -0
- package/lib/infrastructure/logto/handle-sign-up.js +28 -0
- package/lib/infrastructure/logto/index.d.ts +32 -0
- package/lib/infrastructure/logto/index.js +21 -0
- package/lib/useCases/getContext/GetContextController.d.ts +13 -0
- package/lib/useCases/getContext/GetContextController.js +19 -0
- package/lib/useCases/getContext/GetContextController.spec.d.ts +1 -0
- package/lib/useCases/getContext/GetContextController.spec.js +15 -0
- package/lib/useCases/getContext/GetContextUseCase.d.ts +15 -0
- package/lib/useCases/getContext/GetContextUseCase.js +11 -0
- package/lib/useCases/getContext/GetContextUseCase.spec.d.ts +1 -0
- package/lib/useCases/getContext/GetContextUseCase.spec.js +20 -0
- package/lib/useCases/getContext/index.d.ts +9 -0
- package/lib/useCases/getContext/index.js +17 -0
- package/lib/useCases/handleAuthRoutes/HandleAuthRoutesError.d.ts +9 -0
- package/lib/useCases/handleAuthRoutes/HandleAuthRoutesError.js +19 -0
- package/lib/useCases/handleAuthRoutes/index.d.ts +15 -0
- package/lib/useCases/handleAuthRoutes/index.js +46 -0
- package/lib/useCases/handleSignIn/HandleSignInController.d.ts +14 -0
- package/lib/useCases/handleSignIn/HandleSignInController.js +29 -0
- package/lib/useCases/handleSignIn/HandleSignInController.spec.d.ts +1 -0
- package/lib/useCases/handleSignIn/HandleSignInController.spec.js +16 -0
- package/lib/useCases/handleSignIn/HandleSignInUseCase.d.ts +16 -0
- package/lib/useCases/handleSignIn/HandleSignInUseCase.js +15 -0
- package/lib/useCases/handleSignIn/HandleSignInUseCase.spec.d.ts +1 -0
- package/lib/useCases/handleSignIn/HandleSignInUseCase.spec.js +18 -0
- package/lib/useCases/handleSignIn/index.d.ts +11 -0
- package/lib/useCases/handleSignIn/index.js +17 -0
- package/lib/useCases/handleSignInCallback/HandleSignInCallbackController.d.ts +14 -0
- package/lib/useCases/handleSignInCallback/HandleSignInCallbackController.js +40 -0
- package/lib/useCases/handleSignInCallback/HandleSignInCallbackController.spec.d.ts +1 -0
- package/lib/useCases/handleSignInCallback/HandleSignInCallbackController.spec.js +16 -0
- package/lib/useCases/handleSignInCallback/HandleSignInCallbackError.d.ts +8 -0
- package/lib/useCases/handleSignInCallback/HandleSignInCallbackError.js +15 -0
- package/lib/useCases/handleSignInCallback/HandleSignInCallbackUseCase.d.ts +15 -0
- package/lib/useCases/handleSignInCallback/HandleSignInCallbackUseCase.js +14 -0
- package/lib/useCases/handleSignInCallback/HandleSignInCallbackUseCase.spec.d.ts +1 -0
- package/lib/useCases/handleSignInCallback/HandleSignInCallbackUseCase.spec.js +18 -0
- package/lib/useCases/handleSignInCallback/index.d.ts +11 -0
- package/lib/useCases/handleSignInCallback/index.js +17 -0
- package/lib/useCases/handleSignOut/HandleSignOutController.d.ts +14 -0
- package/lib/useCases/handleSignOut/HandleSignOutController.js +32 -0
- package/lib/useCases/handleSignOut/HandleSignOutController.spec.d.ts +1 -0
- package/lib/useCases/handleSignOut/HandleSignOutController.spec.js +16 -0
- package/lib/useCases/handleSignOut/HandleSignOutError.d.ts +8 -0
- package/lib/useCases/handleSignOut/HandleSignOutError.js +15 -0
- package/lib/useCases/handleSignOut/HandleSignOutUseCase.d.ts +16 -0
- package/lib/useCases/handleSignOut/HandleSignOutUseCase.js +15 -0
- package/lib/useCases/handleSignOut/HandleSignOutUseCase.spec.d.ts +1 -0
- package/lib/useCases/handleSignOut/HandleSignOutUseCase.spec.js +18 -0
- package/lib/useCases/handleSignOut/index.d.ts +11 -0
- package/lib/useCases/handleSignOut/index.js +17 -0
- package/lib/useCases/handleSignUp/HandleSignUpController.d.ts +14 -0
- package/lib/useCases/handleSignUp/HandleSignUpController.js +29 -0
- package/lib/useCases/handleSignUp/HandleSignUpController.spec.d.ts +1 -0
- package/lib/useCases/handleSignUp/HandleSignUpController.spec.js +16 -0
- package/lib/useCases/handleSignUp/HandleSignUpUseCase.d.ts +16 -0
- package/lib/useCases/handleSignUp/HandleSignUpUseCase.js +15 -0
- package/lib/useCases/handleSignUp/HandleSignUpUseCase.spec.d.ts +1 -0
- package/lib/useCases/handleSignUp/HandleSignUpUseCase.spec.js +18 -0
- package/lib/useCases/handleSignUp/index.d.ts +11 -0
- package/lib/useCases/handleSignUp/index.js +17 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Silverhand
|
|
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,144 @@
|
|
|
1
|
+
# Logto React Router SDK
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@logto/react-router)
|
|
4
|
+
[](https://github.com/logto-io/js/actions/workflows/main.yml)
|
|
5
|
+
[](https://app.codecov.io/gh/logto-io/js?branch=master)
|
|
6
|
+
|
|
7
|
+
The Logto React Router SDK written in TypeScript.
|
|
8
|
+
|
|
9
|
+
> **Note:** This SDK has been migrated from Remix to React Router. For detailed migration guide, please refer to the [official React Router migration documentation](https://reactrouter.com/upgrading/remix).
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
**Note:** This package requires Node.js version 20 or higher.
|
|
14
|
+
|
|
15
|
+
### Using npm
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @logto/react-router
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Using yarn
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
yarn add @logto/react-router
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Using pnpm
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pnpm add @logto/react-router
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
Before initializing the SDK, we have to create a `SessionStorage` instance which takes care of the session persistence. In our case, we want to use a cookie-based session:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
// services/auth.server.ts
|
|
39
|
+
import { makeLogtoReactRouter } from '@logto/react-router';
|
|
40
|
+
import { createCookieSessionStorage } from 'react-router';
|
|
41
|
+
|
|
42
|
+
const sessionStorage = createCookieSessionStorage({
|
|
43
|
+
cookie: {
|
|
44
|
+
name: 'logto-session',
|
|
45
|
+
maxAge: 14 * 24 * 60 * 60,
|
|
46
|
+
secrets: ['secr3tSession'],
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export const logto = makeLogtoReactRouter(
|
|
51
|
+
{
|
|
52
|
+
endpoint: process.env.LOGTO_ENDPOINT!,
|
|
53
|
+
appId: process.env.LOGTO_APP_ID!,
|
|
54
|
+
appSecret: process.env.LOGTO_APP_SECRET!,
|
|
55
|
+
baseUrl: process.env.LOGTO_BASE_URL!,
|
|
56
|
+
},
|
|
57
|
+
{ sessionStorage }
|
|
58
|
+
);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Whereas the environment variables reflect the respective configuration of the application in Logto.
|
|
62
|
+
|
|
63
|
+
### Setup file-based routing
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
// app/routes.ts
|
|
67
|
+
import { type RouteConfig } from '@react-router/dev/routes';
|
|
68
|
+
import { flatRoutes } from '@react-router/fs-routes';
|
|
69
|
+
|
|
70
|
+
export default flatRoutes() satisfies RouteConfig;
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
This will generate the routes for you based on the files in the `app/routes` directory.
|
|
74
|
+
|
|
75
|
+
### Mounting the authentication route handlers
|
|
76
|
+
|
|
77
|
+
The SDK ships with a convenient function that mounts the authentication routes: sign-in, sign-in callback and the sign-out route. Create a file `routes/api.logto.$action.ts`
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
// routes/api.logto.$action.ts
|
|
81
|
+
|
|
82
|
+
import { logto } from '../../services/auth.server';
|
|
83
|
+
|
|
84
|
+
export const loader = logto.handleAuthRoutes({
|
|
85
|
+
'sign-in': {
|
|
86
|
+
path: '/api/logto/sign-in',
|
|
87
|
+
redirectBackTo: '/api/logto/callback',
|
|
88
|
+
},
|
|
89
|
+
'sign-in-callback': {
|
|
90
|
+
path: '/api/logto/callback',
|
|
91
|
+
redirectBackTo: '/',
|
|
92
|
+
},
|
|
93
|
+
'sign-out': {
|
|
94
|
+
path: '/api/logto/sign-out',
|
|
95
|
+
redirectBackTo: '/',
|
|
96
|
+
},
|
|
97
|
+
'sign-up': {
|
|
98
|
+
path: '/api/logto/sign-up',
|
|
99
|
+
redirectBackTo: '/api/logto/callback',
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
As you can see, the mount process is configurable and you can adjust it for your particular route structure. The whole URL path structure can be customized via the passed configuration object.
|
|
105
|
+
|
|
106
|
+
When mounting the routes as described above, you can navigate your browser to `/api/logto/sign-in` and you should be redirected to your Logto instance where you have to authenticate then.
|
|
107
|
+
|
|
108
|
+
### Get the authentication context
|
|
109
|
+
|
|
110
|
+
A typical use case is to fetch the _authentication context_ which contains information about the respective user. With that information, it is possible to decide if the user is authenticated or not. The SDK exposes a function that can be used in a React Router `loader` function:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
// app/routes/_index.tsx
|
|
114
|
+
import { type LogtoContext } from '@logto/react-router';
|
|
115
|
+
import { Link, type LoaderFunctionArgs } from 'react-router';
|
|
116
|
+
|
|
117
|
+
import { logto } from '../../services/auth.server';
|
|
118
|
+
|
|
119
|
+
type LoaderResponse = {
|
|
120
|
+
readonly context: LogtoContext;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
|
124
|
+
const context = await logto.getContext({ getAccessToken: false })(request);
|
|
125
|
+
|
|
126
|
+
if (!context.isAuthenticated) {
|
|
127
|
+
return redirect('/api/logto/sign-in');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return { context };
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const Home = ({ loaderData }: { readonly loaderData: LoaderResponse }) => {
|
|
134
|
+
const { context } = loaderData;
|
|
135
|
+
const { isAuthenticated, claims } = context;
|
|
136
|
+
|
|
137
|
+
return <div>Protected Route.</div>;
|
|
138
|
+
};
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Resources
|
|
142
|
+
|
|
143
|
+
[](https://logto.io/)
|
|
144
|
+
[](https://discord.gg/UEPaF3j5e6)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getCookieHeaderFromRequest: (request: Request) => string | null;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Session, SessionStorage } from 'react-router';
|
|
2
|
+
import type { CreateLogtoAdapter, LogtoContext } from '../infrastructure/logto/index.js';
|
|
3
|
+
export declare const getContext: import("vitest").Mock<() => Promise<{
|
|
4
|
+
context: LogtoContext;
|
|
5
|
+
}>>;
|
|
6
|
+
export declare const storage: import("../infrastructure/logto/create-storage.js").LogtoStorage;
|
|
7
|
+
export declare const handleSignIn: import("vitest").Mock<() => Promise<{
|
|
8
|
+
session: Session<import("react-router").SessionData, import("react-router").SessionData>;
|
|
9
|
+
navigateToUrl: string;
|
|
10
|
+
}>>;
|
|
11
|
+
export declare const handleSignInCallback: import("vitest").Mock<() => Promise<{
|
|
12
|
+
session: Session<import("react-router").SessionData, import("react-router").SessionData>;
|
|
13
|
+
}>>;
|
|
14
|
+
export declare const handleSignOut: import("vitest").Mock<() => Promise<{
|
|
15
|
+
navigateToUrl: string;
|
|
16
|
+
}>>;
|
|
17
|
+
export declare const handleSignUp: import("vitest").Mock<() => Promise<{
|
|
18
|
+
session: Session<import("react-router").SessionData, import("react-router").SessionData>;
|
|
19
|
+
navigateToUrl: string;
|
|
20
|
+
}>>;
|
|
21
|
+
export declare const createLogtoAdapter: CreateLogtoAdapter;
|
|
22
|
+
export declare const commitSession: import("vitest").Mock<(session: Session) => Promise<string>>;
|
|
23
|
+
export declare const destroySession: import("vitest").Mock<(...args: any[]) => any>;
|
|
24
|
+
export declare const getSession: import("vitest").Mock<(...args: any[]) => any>;
|
|
25
|
+
export declare const sessionStorage: SessionStorage;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createSession } from 'react-router';
|
|
2
|
+
import { createStorage } from '../infrastructure/logto/create-storage.js';
|
|
3
|
+
const context = {
|
|
4
|
+
isAuthenticated: true,
|
|
5
|
+
claims: {
|
|
6
|
+
email: 'test@test.io',
|
|
7
|
+
aud: '',
|
|
8
|
+
exp: 1_665_684_317,
|
|
9
|
+
iat: 1_665_684_318,
|
|
10
|
+
iss: '',
|
|
11
|
+
sub: '',
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
export const getContext = vi.fn(async () => ({
|
|
15
|
+
context,
|
|
16
|
+
}));
|
|
17
|
+
const session = createSession();
|
|
18
|
+
export const storage = createStorage(session);
|
|
19
|
+
export const handleSignIn = vi.fn(async () => ({
|
|
20
|
+
session,
|
|
21
|
+
navigateToUrl: '/success-handle-sign-in',
|
|
22
|
+
}));
|
|
23
|
+
export const handleSignInCallback = vi.fn(async () => ({
|
|
24
|
+
session,
|
|
25
|
+
}));
|
|
26
|
+
export const handleSignOut = vi.fn(async () => ({
|
|
27
|
+
navigateToUrl: '/success-handle-sign-out',
|
|
28
|
+
}));
|
|
29
|
+
export const handleSignUp = vi.fn(async () => ({
|
|
30
|
+
session,
|
|
31
|
+
navigateToUrl: '/success-handle-sign-up',
|
|
32
|
+
}));
|
|
33
|
+
export const createLogtoAdapter = vi.fn((session) => {
|
|
34
|
+
return {
|
|
35
|
+
handleSignIn,
|
|
36
|
+
handleSignInCallback,
|
|
37
|
+
handleSignOut,
|
|
38
|
+
handleSignUp,
|
|
39
|
+
getContext,
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
43
|
+
export const commitSession = vi.fn(async (session) => session.data);
|
|
44
|
+
export const destroySession = vi.fn();
|
|
45
|
+
export const getSession = vi.fn();
|
|
46
|
+
export const sessionStorage = {
|
|
47
|
+
commitSession,
|
|
48
|
+
destroySession,
|
|
49
|
+
getSession,
|
|
50
|
+
};
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { GetContextParameters, LogtoConfig } from '@logto/node';
|
|
2
|
+
import type { SessionStorage } from 'react-router';
|
|
3
|
+
type Config = Readonly<LogtoConfig> & {
|
|
4
|
+
readonly baseUrl: string;
|
|
5
|
+
};
|
|
6
|
+
export declare const makeLogtoReactRouter: (config: Config, deps: {
|
|
7
|
+
sessionStorage: SessionStorage;
|
|
8
|
+
}) => Readonly<{
|
|
9
|
+
handleAuthRoutes: (dto: {
|
|
10
|
+
"sign-in": {
|
|
11
|
+
readonly path: string;
|
|
12
|
+
readonly redirectBackTo: string;
|
|
13
|
+
};
|
|
14
|
+
"sign-in-callback": {
|
|
15
|
+
readonly path: string;
|
|
16
|
+
readonly redirectBackTo: string;
|
|
17
|
+
};
|
|
18
|
+
"sign-out": {
|
|
19
|
+
readonly path: string;
|
|
20
|
+
readonly redirectBackTo: string;
|
|
21
|
+
};
|
|
22
|
+
"sign-up": {
|
|
23
|
+
readonly path: string;
|
|
24
|
+
readonly redirectBackTo: string;
|
|
25
|
+
};
|
|
26
|
+
}) => import("react-router").LoaderFunction;
|
|
27
|
+
getContext: (dto: GetContextParameters) => (request: Request) => Promise<import("@logto/node").LogtoContext>;
|
|
28
|
+
}>;
|
|
29
|
+
export type { AccessTokenClaims, IdTokenClaims, LogtoContext, InteractionMode, LogtoErrorCode, UserInfoResponse, } from '@logto/node';
|
|
30
|
+
export { LogtoError, LogtoRequestError, LogtoClientError, OidcError, Prompt, ReservedScope, UserScope, organizationUrnPrefix, buildOrganizationUrn, getOrganizationIdFromUrn, PersistKey, } from '@logto/node';
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { makeLogtoAdapter } from './infrastructure/logto/index.js';
|
|
2
|
+
import { makeGetContext } from './useCases/getContext/index.js';
|
|
3
|
+
import { makeHandleAuthRoutes } from './useCases/handleAuthRoutes/index.js';
|
|
4
|
+
export { LogtoClientError, LogtoError, LogtoRequestError, OidcError, PersistKey, Prompt, ReservedScope, UserScope, buildOrganizationUrn, getOrganizationIdFromUrn, organizationUrnPrefix } from '@logto/node';
|
|
5
|
+
|
|
6
|
+
const makeLogtoReactRouter = (config, deps) => {
|
|
7
|
+
const { sessionStorage } = deps;
|
|
8
|
+
const { baseUrl } = config;
|
|
9
|
+
const createLogtoAdapter = makeLogtoAdapter(config);
|
|
10
|
+
return Object.freeze({
|
|
11
|
+
handleAuthRoutes: makeHandleAuthRoutes({
|
|
12
|
+
baseUrl,
|
|
13
|
+
createLogtoAdapter,
|
|
14
|
+
sessionStorage,
|
|
15
|
+
}),
|
|
16
|
+
getContext: (dto) => makeGetContext(dto, {
|
|
17
|
+
createLogtoAdapter,
|
|
18
|
+
sessionStorage,
|
|
19
|
+
}),
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export { makeLogtoReactRouter };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { LogtoConfig } from '@logto/node';
|
|
2
|
+
import LogtoClient from '@logto/node';
|
|
3
|
+
import type { LogtoStorage } from './create-storage.js';
|
|
4
|
+
export declare const makeLogtoClient: (config: LogtoConfig, storage: LogtoStorage) => (navigate?: (url: string) => void) => LogtoClient;
|
|
5
|
+
export type CreateLogtoClient = ReturnType<typeof makeLogtoClient>;
|
|
6
|
+
export { type LogtoConfig } from '@logto/node';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import LogtoClient from '@logto/node';
|
|
2
|
+
|
|
3
|
+
const makeLogtoClient = (config, storage) =>
|
|
4
|
+
// Have to deactivate the eslint rule here as the `LogtoClient`
|
|
5
|
+
// awaits a `navigate` function.
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
7
|
+
(navigate = () => { }) => {
|
|
8
|
+
return new LogtoClient(config, { storage, navigate });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export { makeLogtoClient };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createSession } from 'react-router';
|
|
2
|
+
import { makeLogtoClient } from './create-client.js';
|
|
3
|
+
import { createStorage } from './create-storage.js';
|
|
4
|
+
const config = {
|
|
5
|
+
appId: 'app_id_value',
|
|
6
|
+
endpoint: 'https://logto.dev',
|
|
7
|
+
};
|
|
8
|
+
describe('infrastructure:logto:createClient', () => {
|
|
9
|
+
it('creates an instance without crash', () => {
|
|
10
|
+
expect(() => {
|
|
11
|
+
const storage = createStorage(createSession());
|
|
12
|
+
const createLogtoClient = makeLogtoClient(config, storage);
|
|
13
|
+
createLogtoClient();
|
|
14
|
+
}).not.toThrow();
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Storage, StorageKey } from '@logto/node';
|
|
2
|
+
import type { Session } from 'react-router';
|
|
3
|
+
declare class LogtoStorage implements Storage<StorageKey> {
|
|
4
|
+
private readonly properties;
|
|
5
|
+
static readonly fromSession: (session: Session) => LogtoStorage;
|
|
6
|
+
readonly session: Session<import("react-router").SessionData, import("react-router").SessionData>;
|
|
7
|
+
private constructor();
|
|
8
|
+
readonly setItem: (key: StorageKey, value: string) => Promise<void>;
|
|
9
|
+
readonly getItem: (key: StorageKey) => Promise<string | null>;
|
|
10
|
+
readonly removeItem: (key: StorageKey) => Promise<void>;
|
|
11
|
+
readonly save: () => Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export declare const createStorage: (session: Session) => LogtoStorage;
|
|
14
|
+
export type { LogtoStorage };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
class LogtoStorage {
|
|
2
|
+
static { this.fromSession = (session) => {
|
|
3
|
+
return new LogtoStorage({ session });
|
|
4
|
+
}; }
|
|
5
|
+
constructor(properties) {
|
|
6
|
+
this.properties = properties;
|
|
7
|
+
this.session = this.properties.session;
|
|
8
|
+
this.setItem = async (key, value) => {
|
|
9
|
+
this.session.set(key, value);
|
|
10
|
+
};
|
|
11
|
+
this.getItem = async (key) => {
|
|
12
|
+
const itemExists = this.session.has(key);
|
|
13
|
+
if (!itemExists) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return String(this.session.get(key));
|
|
17
|
+
};
|
|
18
|
+
this.removeItem = async (key) => {
|
|
19
|
+
this.session.unset(key);
|
|
20
|
+
};
|
|
21
|
+
this.save = async () => {
|
|
22
|
+
// Not required as the persistence happens in the integration layer
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const createStorage = (session) => LogtoStorage.fromSession(session);
|
|
27
|
+
|
|
28
|
+
export { createStorage };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createSession } from 'react-router';
|
|
2
|
+
import { createStorage } from './create-storage.js';
|
|
3
|
+
describe('infrastructure:logto:createStorage', () => {
|
|
4
|
+
it('can create a LogtoStorage instance', async () => {
|
|
5
|
+
const session = createSession();
|
|
6
|
+
const storage = createStorage(session);
|
|
7
|
+
expect(storage.constructor.name).toBe('LogtoStorage');
|
|
8
|
+
});
|
|
9
|
+
it('can set items', async () => {
|
|
10
|
+
const session = createSession();
|
|
11
|
+
const storage = createStorage(session);
|
|
12
|
+
await storage.setItem('idToken', 'a');
|
|
13
|
+
await storage.setItem('accessToken', 'b');
|
|
14
|
+
await storage.setItem('refreshToken', 'c');
|
|
15
|
+
await storage.setItem('signInSession', 'd');
|
|
16
|
+
expect(session.data.idToken).toBe('a');
|
|
17
|
+
expect(session.data.accessToken).toBe('b');
|
|
18
|
+
expect(session.data.refreshToken).toBe('c');
|
|
19
|
+
expect(session.data.signInSession).toBe('d');
|
|
20
|
+
});
|
|
21
|
+
it('can remove items', async () => {
|
|
22
|
+
const session = createSession();
|
|
23
|
+
const storage = createStorage(session);
|
|
24
|
+
await storage.setItem('idToken', 'a');
|
|
25
|
+
await storage.setItem('accessToken', 'b');
|
|
26
|
+
await storage.removeItem('accessToken');
|
|
27
|
+
expect(session.data.idToken).toBe('a');
|
|
28
|
+
expect(session.data.accessToken).toBeUndefined();
|
|
29
|
+
});
|
|
30
|
+
it('can get items', async () => {
|
|
31
|
+
const session = createSession();
|
|
32
|
+
const storage = createStorage(session);
|
|
33
|
+
await storage.setItem('accessToken', 'b');
|
|
34
|
+
const accessToken = await storage.getItem('accessToken');
|
|
35
|
+
const refreshToken = await storage.getItem('refreshToken');
|
|
36
|
+
expect(accessToken).toBe('b');
|
|
37
|
+
expect(refreshToken).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { GetContextParameters, LogtoContext } from '@logto/node';
|
|
2
|
+
import type { CreateLogtoClient } from './create-client.js';
|
|
3
|
+
import type { LogtoStorage } from './create-storage.js';
|
|
4
|
+
type GetContextResponse = {
|
|
5
|
+
readonly context: LogtoContext;
|
|
6
|
+
};
|
|
7
|
+
export declare const makeGetContext: (deps: {
|
|
8
|
+
storage: LogtoStorage;
|
|
9
|
+
createClient: CreateLogtoClient;
|
|
10
|
+
}) => (request: GetContextParameters) => Promise<GetContextResponse>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Session } from 'react-router';
|
|
2
|
+
import type { CreateLogtoClient } from './create-client.js';
|
|
3
|
+
import type { LogtoStorage } from './create-storage.js';
|
|
4
|
+
type HandleSignInCallbackRequest = {
|
|
5
|
+
callbackUri: string;
|
|
6
|
+
};
|
|
7
|
+
type HandleSignInCallbackResponse = {
|
|
8
|
+
readonly session: Session;
|
|
9
|
+
};
|
|
10
|
+
export declare const makeHandleSignInCallback: (deps: {
|
|
11
|
+
storage: LogtoStorage;
|
|
12
|
+
createClient: CreateLogtoClient;
|
|
13
|
+
}) => (request: HandleSignInCallbackRequest) => Promise<HandleSignInCallbackResponse>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const makeHandleSignInCallback = (deps) => async (request) => {
|
|
2
|
+
const { storage, createClient } = deps;
|
|
3
|
+
const client = createClient();
|
|
4
|
+
await client.handleSignInCallback(request.callbackUri);
|
|
5
|
+
return {
|
|
6
|
+
session: storage.session,
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export { makeHandleSignInCallback };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Session } from 'react-router';
|
|
2
|
+
import type { CreateLogtoClient } from './create-client.js';
|
|
3
|
+
import type { LogtoStorage } from './create-storage.js';
|
|
4
|
+
type HandleSignInRequest = {
|
|
5
|
+
readonly redirectUri: string;
|
|
6
|
+
};
|
|
7
|
+
type HandleSignInResponse = {
|
|
8
|
+
readonly session: Session;
|
|
9
|
+
readonly navigateToUrl: string;
|
|
10
|
+
};
|
|
11
|
+
export declare const makeHandleSignIn: (deps: {
|
|
12
|
+
storage: LogtoStorage;
|
|
13
|
+
createClient: CreateLogtoClient;
|
|
14
|
+
}) => (request: HandleSignInRequest) => Promise<HandleSignInResponse>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
class HandleSignInCommand {
|
|
2
|
+
static { this.fromDependencies = (dependencies) => new HandleSignInCommand({ createClient: dependencies.createClient }); }
|
|
3
|
+
constructor(properties) {
|
|
4
|
+
this.properties = properties;
|
|
5
|
+
this.navigateToUrl = '/api/sign-in';
|
|
6
|
+
}
|
|
7
|
+
async execute(request) {
|
|
8
|
+
const { createClient } = this.properties;
|
|
9
|
+
const client = createClient((url) => {
|
|
10
|
+
this.navigateToUrl = url;
|
|
11
|
+
});
|
|
12
|
+
await client.signIn(request.redirectUri);
|
|
13
|
+
return {
|
|
14
|
+
navigateToUrl: this.navigateToUrl,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const makeHandleSignIn = (deps) => async (request) => {
|
|
19
|
+
const { storage, createClient } = deps;
|
|
20
|
+
const command = HandleSignInCommand.fromDependencies({ createClient });
|
|
21
|
+
const { navigateToUrl } = await command.execute(request);
|
|
22
|
+
return {
|
|
23
|
+
session: storage.session,
|
|
24
|
+
navigateToUrl,
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export { makeHandleSignIn };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CreateLogtoClient } from './create-client.js';
|
|
2
|
+
type HandleSignOutRequest = {
|
|
3
|
+
readonly redirectUri: string;
|
|
4
|
+
};
|
|
5
|
+
type HandleSignOutResponse = {
|
|
6
|
+
readonly navigateToUrl: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const makeHandleSignOut: (deps: {
|
|
9
|
+
createClient: CreateLogtoClient;
|
|
10
|
+
}) => (request: HandleSignOutRequest) => Promise<HandleSignOutResponse>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
class HandleSignOutCommand {
|
|
2
|
+
static { this.fromDependencies = (dependencies) => new HandleSignOutCommand({ createClient: dependencies.createClient }); }
|
|
3
|
+
constructor(properties) {
|
|
4
|
+
this.properties = properties;
|
|
5
|
+
this.navigateToUrl = '/api/sign-in';
|
|
6
|
+
}
|
|
7
|
+
async execute(request) {
|
|
8
|
+
const { createClient } = this.properties;
|
|
9
|
+
const client = createClient((url) => {
|
|
10
|
+
this.navigateToUrl = url;
|
|
11
|
+
});
|
|
12
|
+
await client.signOut(request.redirectUri);
|
|
13
|
+
return {
|
|
14
|
+
navigateToUrl: this.navigateToUrl,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const makeHandleSignOut = (deps) => async (request) => {
|
|
19
|
+
const { createClient } = deps;
|
|
20
|
+
const command = HandleSignOutCommand.fromDependencies({ createClient });
|
|
21
|
+
return command.execute(request);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export { makeHandleSignOut };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Session } from 'react-router';
|
|
2
|
+
import type { CreateLogtoClient } from './create-client.js';
|
|
3
|
+
import type { LogtoStorage } from './create-storage.js';
|
|
4
|
+
type HandleSignUpRequest = {
|
|
5
|
+
readonly redirectUri: string;
|
|
6
|
+
};
|
|
7
|
+
type HandleSignUpResponse = {
|
|
8
|
+
readonly session: Session;
|
|
9
|
+
readonly navigateToUrl: string;
|
|
10
|
+
};
|
|
11
|
+
export declare const makeHandleSignUp: (deps: {
|
|
12
|
+
storage: LogtoStorage;
|
|
13
|
+
createClient: CreateLogtoClient;
|
|
14
|
+
}) => (request: HandleSignUpRequest) => Promise<HandleSignUpResponse>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
class HandleSignUpCommand {
|
|
2
|
+
static { this.fromDependencies = (dependencies) => new HandleSignUpCommand({ createClient: dependencies.createClient }); }
|
|
3
|
+
constructor(properties) {
|
|
4
|
+
this.properties = properties;
|
|
5
|
+
this.navigateToUrl = '/api/sign-up';
|
|
6
|
+
}
|
|
7
|
+
async execute(request) {
|
|
8
|
+
const { createClient } = this.properties;
|
|
9
|
+
const client = createClient((url) => {
|
|
10
|
+
this.navigateToUrl = url;
|
|
11
|
+
});
|
|
12
|
+
await client.signIn({ redirectUri: request.redirectUri, interactionMode: 'signUp' });
|
|
13
|
+
return {
|
|
14
|
+
navigateToUrl: this.navigateToUrl,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const makeHandleSignUp = (deps) => async (request) => {
|
|
19
|
+
const { storage, createClient } = deps;
|
|
20
|
+
const command = HandleSignUpCommand.fromDependencies({ createClient });
|
|
21
|
+
const { navigateToUrl } = await command.execute(request);
|
|
22
|
+
return {
|
|
23
|
+
session: storage.session,
|
|
24
|
+
navigateToUrl,
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export { makeHandleSignUp };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { LogtoConfig } from '@logto/node';
|
|
2
|
+
import type { Session } from 'react-router';
|
|
3
|
+
type MakeLogtoAdapterConfiguration = LogtoConfig;
|
|
4
|
+
export declare const makeLogtoAdapter: (config: MakeLogtoAdapterConfiguration) => (session: Session) => {
|
|
5
|
+
handleSignIn: (request: {
|
|
6
|
+
readonly redirectUri: string;
|
|
7
|
+
}) => Promise<{
|
|
8
|
+
readonly session: Session<import("react-router").SessionData, import("react-router").SessionData>;
|
|
9
|
+
readonly navigateToUrl: string;
|
|
10
|
+
}>;
|
|
11
|
+
handleSignInCallback: (request: {
|
|
12
|
+
callbackUri: string;
|
|
13
|
+
}) => Promise<{
|
|
14
|
+
readonly session: Session<import("react-router").SessionData, import("react-router").SessionData>;
|
|
15
|
+
}>;
|
|
16
|
+
handleSignOut: (request: {
|
|
17
|
+
readonly redirectUri: string;
|
|
18
|
+
}) => Promise<{
|
|
19
|
+
readonly navigateToUrl: string;
|
|
20
|
+
}>;
|
|
21
|
+
handleSignUp: (request: {
|
|
22
|
+
readonly redirectUri: string;
|
|
23
|
+
}) => Promise<{
|
|
24
|
+
readonly session: Session<import("react-router").SessionData, import("react-router").SessionData>;
|
|
25
|
+
readonly navigateToUrl: string;
|
|
26
|
+
}>;
|
|
27
|
+
getContext: (request: import("@logto/node").GetContextParameters) => Promise<{
|
|
28
|
+
readonly context: import("@logto/node").LogtoContext;
|
|
29
|
+
}>;
|
|
30
|
+
};
|
|
31
|
+
export type CreateLogtoAdapter = ReturnType<typeof makeLogtoAdapter>;
|
|
32
|
+
export { type LogtoContext } from '@logto/node';
|