@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.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +144 -0
  3. package/lib/framework/get-cookie-header-from-request.d.ts +1 -0
  4. package/lib/framework/get-cookie-header-from-request.js +3 -0
  5. package/lib/framework/mocks.d.ts +25 -0
  6. package/lib/framework/mocks.js +50 -0
  7. package/lib/index.d.ts +30 -0
  8. package/lib/index.js +23 -0
  9. package/lib/infrastructure/logto/create-client.d.ts +6 -0
  10. package/lib/infrastructure/logto/create-client.js +11 -0
  11. package/lib/infrastructure/logto/create-client.spec.d.ts +1 -0
  12. package/lib/infrastructure/logto/create-client.spec.js +16 -0
  13. package/lib/infrastructure/logto/create-storage.d.ts +14 -0
  14. package/lib/infrastructure/logto/create-storage.js +28 -0
  15. package/lib/infrastructure/logto/create-storage.spec.d.ts +1 -0
  16. package/lib/infrastructure/logto/create-storage.spec.js +39 -0
  17. package/lib/infrastructure/logto/get-context.d.ts +11 -0
  18. package/lib/infrastructure/logto/get-context.js +10 -0
  19. package/lib/infrastructure/logto/handle-sign-in-callback.d.ts +14 -0
  20. package/lib/infrastructure/logto/handle-sign-in-callback.js +10 -0
  21. package/lib/infrastructure/logto/handle-sign-in.d.ts +15 -0
  22. package/lib/infrastructure/logto/handle-sign-in.js +28 -0
  23. package/lib/infrastructure/logto/handle-sign-out.d.ts +11 -0
  24. package/lib/infrastructure/logto/handle-sign-out.js +24 -0
  25. package/lib/infrastructure/logto/handle-sign-up.d.ts +15 -0
  26. package/lib/infrastructure/logto/handle-sign-up.js +28 -0
  27. package/lib/infrastructure/logto/index.d.ts +32 -0
  28. package/lib/infrastructure/logto/index.js +21 -0
  29. package/lib/useCases/getContext/GetContextController.d.ts +13 -0
  30. package/lib/useCases/getContext/GetContextController.js +19 -0
  31. package/lib/useCases/getContext/GetContextController.spec.d.ts +1 -0
  32. package/lib/useCases/getContext/GetContextController.spec.js +15 -0
  33. package/lib/useCases/getContext/GetContextUseCase.d.ts +15 -0
  34. package/lib/useCases/getContext/GetContextUseCase.js +11 -0
  35. package/lib/useCases/getContext/GetContextUseCase.spec.d.ts +1 -0
  36. package/lib/useCases/getContext/GetContextUseCase.spec.js +20 -0
  37. package/lib/useCases/getContext/index.d.ts +9 -0
  38. package/lib/useCases/getContext/index.js +17 -0
  39. package/lib/useCases/handleAuthRoutes/HandleAuthRoutesError.d.ts +9 -0
  40. package/lib/useCases/handleAuthRoutes/HandleAuthRoutesError.js +19 -0
  41. package/lib/useCases/handleAuthRoutes/index.d.ts +15 -0
  42. package/lib/useCases/handleAuthRoutes/index.js +46 -0
  43. package/lib/useCases/handleSignIn/HandleSignInController.d.ts +14 -0
  44. package/lib/useCases/handleSignIn/HandleSignInController.js +29 -0
  45. package/lib/useCases/handleSignIn/HandleSignInController.spec.d.ts +1 -0
  46. package/lib/useCases/handleSignIn/HandleSignInController.spec.js +16 -0
  47. package/lib/useCases/handleSignIn/HandleSignInUseCase.d.ts +16 -0
  48. package/lib/useCases/handleSignIn/HandleSignInUseCase.js +15 -0
  49. package/lib/useCases/handleSignIn/HandleSignInUseCase.spec.d.ts +1 -0
  50. package/lib/useCases/handleSignIn/HandleSignInUseCase.spec.js +18 -0
  51. package/lib/useCases/handleSignIn/index.d.ts +11 -0
  52. package/lib/useCases/handleSignIn/index.js +17 -0
  53. package/lib/useCases/handleSignInCallback/HandleSignInCallbackController.d.ts +14 -0
  54. package/lib/useCases/handleSignInCallback/HandleSignInCallbackController.js +40 -0
  55. package/lib/useCases/handleSignInCallback/HandleSignInCallbackController.spec.d.ts +1 -0
  56. package/lib/useCases/handleSignInCallback/HandleSignInCallbackController.spec.js +16 -0
  57. package/lib/useCases/handleSignInCallback/HandleSignInCallbackError.d.ts +8 -0
  58. package/lib/useCases/handleSignInCallback/HandleSignInCallbackError.js +15 -0
  59. package/lib/useCases/handleSignInCallback/HandleSignInCallbackUseCase.d.ts +15 -0
  60. package/lib/useCases/handleSignInCallback/HandleSignInCallbackUseCase.js +14 -0
  61. package/lib/useCases/handleSignInCallback/HandleSignInCallbackUseCase.spec.d.ts +1 -0
  62. package/lib/useCases/handleSignInCallback/HandleSignInCallbackUseCase.spec.js +18 -0
  63. package/lib/useCases/handleSignInCallback/index.d.ts +11 -0
  64. package/lib/useCases/handleSignInCallback/index.js +17 -0
  65. package/lib/useCases/handleSignOut/HandleSignOutController.d.ts +14 -0
  66. package/lib/useCases/handleSignOut/HandleSignOutController.js +32 -0
  67. package/lib/useCases/handleSignOut/HandleSignOutController.spec.d.ts +1 -0
  68. package/lib/useCases/handleSignOut/HandleSignOutController.spec.js +16 -0
  69. package/lib/useCases/handleSignOut/HandleSignOutError.d.ts +8 -0
  70. package/lib/useCases/handleSignOut/HandleSignOutError.js +15 -0
  71. package/lib/useCases/handleSignOut/HandleSignOutUseCase.d.ts +16 -0
  72. package/lib/useCases/handleSignOut/HandleSignOutUseCase.js +15 -0
  73. package/lib/useCases/handleSignOut/HandleSignOutUseCase.spec.d.ts +1 -0
  74. package/lib/useCases/handleSignOut/HandleSignOutUseCase.spec.js +18 -0
  75. package/lib/useCases/handleSignOut/index.d.ts +11 -0
  76. package/lib/useCases/handleSignOut/index.js +17 -0
  77. package/lib/useCases/handleSignUp/HandleSignUpController.d.ts +14 -0
  78. package/lib/useCases/handleSignUp/HandleSignUpController.js +29 -0
  79. package/lib/useCases/handleSignUp/HandleSignUpController.spec.d.ts +1 -0
  80. package/lib/useCases/handleSignUp/HandleSignUpController.spec.js +16 -0
  81. package/lib/useCases/handleSignUp/HandleSignUpUseCase.d.ts +16 -0
  82. package/lib/useCases/handleSignUp/HandleSignUpUseCase.js +15 -0
  83. package/lib/useCases/handleSignUp/HandleSignUpUseCase.spec.d.ts +1 -0
  84. package/lib/useCases/handleSignUp/HandleSignUpUseCase.spec.js +18 -0
  85. package/lib/useCases/handleSignUp/index.d.ts +11 -0
  86. package/lib/useCases/handleSignUp/index.js +17 -0
  87. package/package.json +63 -0
@@ -0,0 +1,21 @@
1
+ import { makeLogtoClient } from './create-client.js';
2
+ import { createStorage } from './create-storage.js';
3
+ import { makeGetContext } from './get-context.js';
4
+ import { makeHandleSignInCallback } from './handle-sign-in-callback.js';
5
+ import { makeHandleSignIn } from './handle-sign-in.js';
6
+ import { makeHandleSignOut } from './handle-sign-out.js';
7
+ import { makeHandleSignUp } from './handle-sign-up.js';
8
+
9
+ const makeLogtoAdapter = (config) => (session) => {
10
+ const storage = createStorage(session);
11
+ const createClient = makeLogtoClient(config, storage);
12
+ return {
13
+ handleSignIn: makeHandleSignIn({ storage, createClient }),
14
+ handleSignInCallback: makeHandleSignInCallback({ storage, createClient }),
15
+ handleSignOut: makeHandleSignOut({ createClient }),
16
+ handleSignUp: makeHandleSignUp({ storage, createClient }),
17
+ getContext: makeGetContext({ storage, createClient }),
18
+ };
19
+ };
20
+
21
+ export { makeLogtoAdapter };
@@ -0,0 +1,13 @@
1
+ import type { GetContextParameters, LogtoContext } from '@logto/node';
2
+ import type { GetContextUseCase } from './GetContextUseCase.js';
3
+ type GetContextControllerDto = GetContextParameters & {
4
+ readonly useCase: GetContextUseCase;
5
+ };
6
+ export declare class GetContextController {
7
+ private readonly properties;
8
+ static readonly fromDto: (dto: GetContextControllerDto) => GetContextController;
9
+ private readonly useCase;
10
+ private constructor();
11
+ readonly execute: (request: Request) => Promise<LogtoContext>;
12
+ }
13
+ export {};
@@ -0,0 +1,19 @@
1
+ import { getCookieHeaderFromRequest } from '../../framework/get-cookie-header-from-request.js';
2
+
3
+ class GetContextController {
4
+ static { this.fromDto = (dto) => new GetContextController(dto); }
5
+ constructor(properties) {
6
+ this.properties = properties;
7
+ this.useCase = this.properties.useCase;
8
+ this.execute = async (request) => {
9
+ const cookieHeader = getCookieHeaderFromRequest(request);
10
+ const result = await this.useCase({
11
+ cookieHeader: cookieHeader ?? undefined,
12
+ ...this.properties,
13
+ });
14
+ return result.context;
15
+ };
16
+ }
17
+ }
18
+
19
+ export { GetContextController };
@@ -0,0 +1,15 @@
1
+ import { createLogtoAdapter, sessionStorage } from '../../framework/mocks.js';
2
+ import { GetContextController } from './GetContextController.js';
3
+ import { makeGetContextUseCase } from './GetContextUseCase.js';
4
+ describe('useCases:getContext:GetContextController', () => {
5
+ it('can be created', () => {
6
+ const useCase = makeGetContextUseCase({
7
+ createLogtoAdapter,
8
+ sessionStorage,
9
+ });
10
+ const controller = GetContextController.fromDto({
11
+ useCase,
12
+ });
13
+ expect(controller.constructor.name).toBe('GetContextController');
14
+ });
15
+ });
@@ -0,0 +1,15 @@
1
+ import type { GetContextParameters } from '@logto/node';
2
+ import type { SessionStorage } from 'react-router';
3
+ import type { CreateLogtoAdapter, LogtoContext } from '../../infrastructure/logto/index.js';
4
+ type GetContextRequest = GetContextParameters & {
5
+ readonly cookieHeader: string | undefined;
6
+ };
7
+ type GetContextResponse = {
8
+ context: Readonly<LogtoContext>;
9
+ };
10
+ export declare const makeGetContextUseCase: (deps: {
11
+ createLogtoAdapter: CreateLogtoAdapter;
12
+ sessionStorage: SessionStorage;
13
+ }) => (request: GetContextRequest) => Promise<GetContextResponse>;
14
+ export type GetContextUseCase = ReturnType<typeof makeGetContextUseCase>;
15
+ export {};
@@ -0,0 +1,11 @@
1
+ const makeGetContextUseCase = (deps) => async (request) => {
2
+ const { sessionStorage, createLogtoAdapter } = deps;
3
+ const session = await sessionStorage.getSession(request.cookieHeader);
4
+ const logto = createLogtoAdapter(session);
5
+ const response = await logto.getContext(request);
6
+ return {
7
+ context: response.context,
8
+ };
9
+ };
10
+
11
+ export { makeGetContextUseCase };
@@ -0,0 +1,20 @@
1
+ import { createLogtoAdapter, sessionStorage, getContext, getSession, } from '../../framework/mocks.js';
2
+ import { makeGetContextUseCase } from './GetContextUseCase.js';
3
+ describe('useCases:getContext:GetContextUseCase', () => {
4
+ afterEach(() => {
5
+ vi.resetAllMocks();
6
+ });
7
+ it('can make a use case executer', async () => {
8
+ const execute = makeGetContextUseCase({
9
+ createLogtoAdapter,
10
+ sessionStorage,
11
+ });
12
+ const response = await execute({
13
+ cookieHeader: 'abcd',
14
+ });
15
+ expect(getContext).toBeCalledTimes(1);
16
+ expect(getSession).toBeCalledTimes(1);
17
+ expect(response.context.isAuthenticated).toBe(true);
18
+ expect(response.context.claims?.email).toBe('test@test.io');
19
+ });
20
+ });
@@ -0,0 +1,9 @@
1
+ import type { GetContextParameters } from '@logto/node';
2
+ import type { SessionStorage } from 'react-router';
3
+ import type { CreateLogtoAdapter } from '../../infrastructure/logto/index.js';
4
+ type HandleGetContextDeps = {
5
+ readonly createLogtoAdapter: CreateLogtoAdapter;
6
+ readonly sessionStorage: SessionStorage;
7
+ };
8
+ export declare const makeGetContext: (dto: GetContextParameters, deps: HandleGetContextDeps) => (request: Request) => Promise<import("@logto/node").LogtoContext>;
9
+ export {};
@@ -0,0 +1,17 @@
1
+ import { GetContextController } from './GetContextController.js';
2
+ import { makeGetContextUseCase } from './GetContextUseCase.js';
3
+
4
+ const makeGetContext = (dto, deps) => async (request) => {
5
+ const { createLogtoAdapter, sessionStorage } = deps;
6
+ const useCase = makeGetContextUseCase({
7
+ createLogtoAdapter,
8
+ sessionStorage,
9
+ });
10
+ const controller = GetContextController.fromDto({
11
+ useCase,
12
+ ...dto,
13
+ });
14
+ return controller.execute(request);
15
+ };
16
+
17
+ export { makeGetContext };
@@ -0,0 +1,9 @@
1
+ export declare class HandleAuthRoutesError extends Error {
2
+ private readonly properties;
3
+ static readonly becauseNoConfigForPath: (anticipatedPath: string) => HandleAuthRoutesError;
4
+ static readonly becauseOfUnknownRoute: (anticipatedConfigKey: string) => HandleAuthRoutesError;
5
+ readonly code: number;
6
+ readonly cause: Error | undefined;
7
+ readonly plainMessage: string;
8
+ private constructor();
9
+ }
@@ -0,0 +1,19 @@
1
+ class HandleAuthRoutesError extends Error {
2
+ static { this.becauseNoConfigForPath = (anticipatedPath) => new HandleAuthRoutesError({
3
+ code: 1_665_388_277,
4
+ message: `No configuration available for path "${anticipatedPath}".`,
5
+ }); }
6
+ static { this.becauseOfUnknownRoute = (anticipatedConfigKey) => new HandleAuthRoutesError({
7
+ code: 1_665_388_278,
8
+ message: `The config key "${anticipatedConfigKey}" is invalid.`,
9
+ }); }
10
+ constructor(properties) {
11
+ super(`#[${properties.code}] ${properties.message}`);
12
+ this.properties = properties;
13
+ this.code = this.properties.code;
14
+ this.cause = this.properties.cause;
15
+ this.plainMessage = this.properties.message;
16
+ }
17
+ }
18
+
19
+ export { HandleAuthRoutesError };
@@ -0,0 +1,15 @@
1
+ import type { LoaderFunction, SessionStorage } from 'react-router';
2
+ import type { CreateLogtoAdapter } from '../../infrastructure/logto/index.js';
3
+ type AuthRouteConfig = {
4
+ readonly path: string;
5
+ readonly redirectBackTo: string;
6
+ };
7
+ type PossibleRouteTypes = 'sign-in' | 'sign-in-callback' | 'sign-out' | 'sign-up';
8
+ type HandleAuthRoutesDto = Record<PossibleRouteTypes, AuthRouteConfig>;
9
+ type MakeHandleAuthRoutesDto = {
10
+ readonly baseUrl: string;
11
+ readonly createLogtoAdapter: CreateLogtoAdapter;
12
+ readonly sessionStorage: SessionStorage;
13
+ };
14
+ export declare const makeHandleAuthRoutes: (deps: MakeHandleAuthRoutesDto) => (dto: HandleAuthRoutesDto) => LoaderFunction;
15
+ export {};
@@ -0,0 +1,46 @@
1
+ import { makeHandleSignIn } from '../handleSignIn/index.js';
2
+ import { makeHandleSignInCallback } from '../handleSignInCallback/index.js';
3
+ import { makeHandleSignOut } from '../handleSignOut/index.js';
4
+ import { makeHandleSignUp } from '../handleSignUp/index.js';
5
+ import { HandleAuthRoutesError } from './HandleAuthRoutesError.js';
6
+
7
+ const makeHandleAuthRoutes = (deps) => (dto) => async ({ request }) => {
8
+ const anticipatedPath = new URL(request.url).pathname;
9
+ /* eslint-disable no-restricted-syntax */
10
+ const configKey = Object.keys(dto).find((type) => dto[type].path === anticipatedPath);
11
+ const configExists = Boolean(configKey);
12
+ /* eslint-enable no-restricted-syntax */
13
+ if (!configExists) {
14
+ throw HandleAuthRoutesError.becauseNoConfigForPath(anticipatedPath);
15
+ }
16
+ const { sessionStorage, createLogtoAdapter, baseUrl } = deps;
17
+ const config = dto[configKey];
18
+ switch (configKey) {
19
+ case 'sign-in': {
20
+ const handler = makeHandleSignIn({
21
+ redirectBackTo: `${baseUrl}${config.redirectBackTo}`,
22
+ }, { sessionStorage, createLogtoAdapter });
23
+ return handler(request);
24
+ }
25
+ case 'sign-in-callback': {
26
+ const handler = makeHandleSignInCallback({
27
+ redirectBackTo: `${baseUrl}${config.redirectBackTo}`,
28
+ }, { sessionStorage, createLogtoAdapter });
29
+ return handler(request);
30
+ }
31
+ case 'sign-up': {
32
+ const handler = makeHandleSignUp({
33
+ redirectBackTo: `${baseUrl}${config.redirectBackTo}`,
34
+ }, { sessionStorage, createLogtoAdapter });
35
+ return handler(request);
36
+ }
37
+ case 'sign-out': {
38
+ const handler = makeHandleSignOut({
39
+ redirectBackTo: `${baseUrl}${config.redirectBackTo}`,
40
+ }, { sessionStorage, createLogtoAdapter });
41
+ return handler(request);
42
+ }
43
+ }
44
+ };
45
+
46
+ export { makeHandleAuthRoutes };
@@ -0,0 +1,14 @@
1
+ import type { HandleSignInUseCase } from './HandleSignInUseCase.js';
2
+ type HandleSignInControllerDto = {
3
+ readonly redirectUri: string;
4
+ readonly useCase: HandleSignInUseCase;
5
+ };
6
+ export declare class HandleSignInController {
7
+ private readonly properties;
8
+ static readonly fromDto: (dto: HandleSignInControllerDto) => HandleSignInController;
9
+ private readonly useCase;
10
+ private readonly redirectUri;
11
+ private constructor();
12
+ readonly execute: (request: Request) => Promise<Response>;
13
+ }
14
+ export {};
@@ -0,0 +1,29 @@
1
+ import { redirect } from 'react-router';
2
+ import { getCookieHeaderFromRequest } from '../../framework/get-cookie-header-from-request.js';
3
+
4
+ class HandleSignInController {
5
+ static { this.fromDto = (dto) => new HandleSignInController({
6
+ useCase: dto.useCase,
7
+ redirectUri: dto.redirectUri,
8
+ }); }
9
+ constructor(properties) {
10
+ this.properties = properties;
11
+ this.useCase = this.properties.useCase;
12
+ this.redirectUri = this.properties.redirectUri;
13
+ this.execute = async (request) => {
14
+ const cookieHeader = getCookieHeaderFromRequest(request);
15
+ const { redirectUri } = this;
16
+ const result = await this.useCase({
17
+ cookieHeader: cookieHeader ?? undefined,
18
+ redirectUri,
19
+ });
20
+ return redirect(result.navigateToUrl, {
21
+ headers: {
22
+ 'Set-Cookie': result.cookieHeader,
23
+ },
24
+ });
25
+ };
26
+ }
27
+ }
28
+
29
+ export { HandleSignInController };
@@ -0,0 +1,16 @@
1
+ import { createLogtoAdapter, sessionStorage } from '../../framework/mocks.js';
2
+ import { HandleSignInController } from './HandleSignInController.js';
3
+ import { makeHandleSignInUseCase } from './HandleSignInUseCase.js';
4
+ describe('useCases:handleSignIn:HandleSignInController', () => {
5
+ it('can be created', () => {
6
+ const useCase = makeHandleSignInUseCase({
7
+ createLogtoAdapter,
8
+ sessionStorage,
9
+ });
10
+ const controller = HandleSignInController.fromDto({
11
+ useCase,
12
+ redirectUri: '/',
13
+ });
14
+ expect(controller.constructor.name).toBe('HandleSignInController');
15
+ });
16
+ });
@@ -0,0 +1,16 @@
1
+ import type { SessionStorage } from 'react-router';
2
+ import type { CreateLogtoAdapter } from '../../infrastructure/logto/index.js';
3
+ type SignInRequest = {
4
+ readonly cookieHeader: string | undefined;
5
+ readonly redirectUri: string;
6
+ };
7
+ type SignInResponse = {
8
+ readonly cookieHeader: string;
9
+ readonly navigateToUrl: string;
10
+ };
11
+ export declare const makeHandleSignInUseCase: (deps: {
12
+ createLogtoAdapter: CreateLogtoAdapter;
13
+ sessionStorage: SessionStorage;
14
+ }) => (request: SignInRequest) => Promise<SignInResponse>;
15
+ export type HandleSignInUseCase = ReturnType<typeof makeHandleSignInUseCase>;
16
+ export {};
@@ -0,0 +1,15 @@
1
+ const makeHandleSignInUseCase = (deps) => async (request) => {
2
+ const { sessionStorage, createLogtoAdapter } = deps;
3
+ const session = await sessionStorage.getSession(request.cookieHeader);
4
+ const logto = createLogtoAdapter(session);
5
+ const response = await logto.handleSignIn({
6
+ redirectUri: request.redirectUri,
7
+ });
8
+ const cookieHeader = await sessionStorage.commitSession(response.session);
9
+ return {
10
+ cookieHeader,
11
+ navigateToUrl: response.navigateToUrl,
12
+ };
13
+ };
14
+
15
+ export { makeHandleSignInUseCase };
@@ -0,0 +1,18 @@
1
+ import { createLogtoAdapter, sessionStorage, handleSignIn } from '../../framework/mocks.js';
2
+ import { makeHandleSignInUseCase } from './HandleSignInUseCase.js';
3
+ describe('useCases:handleSignIn:HandleSignInUseCase', () => {
4
+ afterEach(() => {
5
+ vi.resetAllMocks();
6
+ });
7
+ it('can make a use case executer', async () => {
8
+ const execute = makeHandleSignInUseCase({
9
+ createLogtoAdapter,
10
+ sessionStorage,
11
+ });
12
+ await execute({
13
+ cookieHeader: 'abcd',
14
+ redirectUri: '/',
15
+ });
16
+ expect(handleSignIn).toBeCalledTimes(1);
17
+ });
18
+ });
@@ -0,0 +1,11 @@
1
+ import type { SessionStorage } from 'react-router';
2
+ import type { CreateLogtoAdapter } from '../../infrastructure/logto/index.js';
3
+ type HandleSignInDto = {
4
+ readonly redirectBackTo: string;
5
+ };
6
+ type HandleSignInDeps = {
7
+ readonly createLogtoAdapter: CreateLogtoAdapter;
8
+ readonly sessionStorage: SessionStorage;
9
+ };
10
+ export declare const makeHandleSignIn: (dto: HandleSignInDto, deps: HandleSignInDeps) => (request: Request) => Promise<Response>;
11
+ export {};
@@ -0,0 +1,17 @@
1
+ import { HandleSignInController } from './HandleSignInController.js';
2
+ import { makeHandleSignInUseCase } from './HandleSignInUseCase.js';
3
+
4
+ const makeHandleSignIn = (dto, deps) => async (request) => {
5
+ const { createLogtoAdapter, sessionStorage } = deps;
6
+ const useCase = makeHandleSignInUseCase({
7
+ createLogtoAdapter,
8
+ sessionStorage,
9
+ });
10
+ const controller = HandleSignInController.fromDto({
11
+ useCase,
12
+ redirectUri: dto.redirectBackTo,
13
+ });
14
+ return controller.execute(request);
15
+ };
16
+
17
+ export { makeHandleSignIn };
@@ -0,0 +1,14 @@
1
+ import type { HandleSignInCallbackUseCase } from './HandleSignInCallbackUseCase.js';
2
+ type HandleSignInCallbackControllerDto = {
3
+ readonly useCase: HandleSignInCallbackUseCase;
4
+ readonly redirectUri: string;
5
+ };
6
+ export declare class HandleSignInCallbackController {
7
+ private readonly properties;
8
+ static readonly fromDto: (dto: HandleSignInCallbackControllerDto) => HandleSignInCallbackController;
9
+ private readonly useCase;
10
+ private readonly redirectUri;
11
+ private constructor();
12
+ readonly execute: (request: Request) => Promise<Response>;
13
+ }
14
+ export {};
@@ -0,0 +1,40 @@
1
+ import { redirect } from 'react-router';
2
+ import { getCookieHeaderFromRequest } from '../../framework/get-cookie-header-from-request.js';
3
+ import { HandleSignInCallbackError } from './HandleSignInCallbackError.js';
4
+
5
+ class HandleSignInCallbackController {
6
+ static { this.fromDto = (dto) => new HandleSignInCallbackController({
7
+ useCase: dto.useCase,
8
+ redirectUri: dto.redirectUri,
9
+ }); }
10
+ constructor(properties) {
11
+ this.properties = properties;
12
+ this.useCase = this.properties.useCase;
13
+ this.redirectUri = this.properties.redirectUri;
14
+ this.execute = async (request) => {
15
+ const cookieHeader = getCookieHeaderFromRequest(request);
16
+ if (!cookieHeader) {
17
+ throw HandleSignInCallbackError.becauseNoCookieHeaderPresent();
18
+ }
19
+ // In some scenarios, like performing the sign-in callback within a Gitpod
20
+ // environment, the load balancer rewrites the URL and uses `http` as the
21
+ // protocol. Here, we make sure that when the `x-forwarded-proto` HTTP header
22
+ // is present, we will replace `http` with `https` in the `callbackUri`.
23
+ const isForwardedHttpsTraffic = request.headers.get('x-forwarded-proto') === 'https';
24
+ const callbackUri = isForwardedHttpsTraffic
25
+ ? request.url.replace('http://', 'https://')
26
+ : request.url;
27
+ const result = await this.useCase({
28
+ cookieHeader,
29
+ callbackUri,
30
+ });
31
+ return redirect(this.redirectUri, {
32
+ headers: {
33
+ 'Set-Cookie': result.cookieHeader,
34
+ },
35
+ });
36
+ };
37
+ }
38
+ }
39
+
40
+ export { HandleSignInCallbackController };
@@ -0,0 +1,16 @@
1
+ import { createLogtoAdapter, sessionStorage } from '../../framework/mocks.js';
2
+ import { HandleSignInCallbackController } from './HandleSignInCallbackController.js';
3
+ import { makeHandleSignInCallbackUseCase } from './HandleSignInCallbackUseCase.js';
4
+ describe('useCases:handleSignInCallback:HandleSignInCallbackController', () => {
5
+ it('can be created', () => {
6
+ const useCase = makeHandleSignInCallbackUseCase({
7
+ createLogtoAdapter,
8
+ sessionStorage,
9
+ });
10
+ const controller = HandleSignInCallbackController.fromDto({
11
+ useCase,
12
+ redirectUri: '/',
13
+ });
14
+ expect(controller.constructor.name).toBe('HandleSignInCallbackController');
15
+ });
16
+ });
@@ -0,0 +1,8 @@
1
+ export declare class HandleSignInCallbackError extends Error {
2
+ private readonly properties;
3
+ static readonly becauseNoCookieHeaderPresent: () => HandleSignInCallbackError;
4
+ readonly code: number;
5
+ readonly cause: Error | undefined;
6
+ readonly plainMessage: string;
7
+ private constructor();
8
+ }
@@ -0,0 +1,15 @@
1
+ class HandleSignInCallbackError extends Error {
2
+ static { this.becauseNoCookieHeaderPresent = () => new HandleSignInCallbackError({
3
+ code: 1_665_388_541,
4
+ message: `The authentication sign-in callback route can't be accessed without a valid cookie.`,
5
+ }); }
6
+ constructor(properties) {
7
+ super(`#[${properties.code}] ${properties.message}`);
8
+ this.properties = properties;
9
+ this.code = this.properties.code;
10
+ this.cause = this.properties.cause;
11
+ this.plainMessage = this.properties.message;
12
+ }
13
+ }
14
+
15
+ export { HandleSignInCallbackError };
@@ -0,0 +1,15 @@
1
+ import type { SessionStorage } from 'react-router';
2
+ import type { CreateLogtoAdapter } from '../../infrastructure/logto/index.js';
3
+ type SignInCallbackRequest = {
4
+ readonly cookieHeader: string;
5
+ readonly callbackUri: string;
6
+ };
7
+ type SignInCallbackResponse = {
8
+ readonly cookieHeader: string;
9
+ };
10
+ export declare const makeHandleSignInCallbackUseCase: (deps: {
11
+ createLogtoAdapter: CreateLogtoAdapter;
12
+ sessionStorage: SessionStorage;
13
+ }) => (request: SignInCallbackRequest) => Promise<SignInCallbackResponse>;
14
+ export type HandleSignInCallbackUseCase = ReturnType<typeof makeHandleSignInCallbackUseCase>;
15
+ export {};
@@ -0,0 +1,14 @@
1
+ const makeHandleSignInCallbackUseCase = (deps) => async (request) => {
2
+ const { sessionStorage, createLogtoAdapter } = deps;
3
+ const session = await sessionStorage.getSession(request.cookieHeader);
4
+ const logto = createLogtoAdapter(session);
5
+ const response = await logto.handleSignInCallback({
6
+ callbackUri: request.callbackUri,
7
+ });
8
+ const cookieHeader = await sessionStorage.commitSession(response.session);
9
+ return {
10
+ cookieHeader,
11
+ };
12
+ };
13
+
14
+ export { makeHandleSignInCallbackUseCase };
@@ -0,0 +1,18 @@
1
+ import { createLogtoAdapter, sessionStorage, handleSignInCallback } from '../../framework/mocks.js';
2
+ import { makeHandleSignInCallbackUseCase } from './HandleSignInCallbackUseCase.js';
3
+ describe('useCases:handleSignInCallback:HandleSignInCallbackUseCase', () => {
4
+ afterEach(() => {
5
+ vi.resetAllMocks();
6
+ });
7
+ it('can make a use case executer', async () => {
8
+ const execute = makeHandleSignInCallbackUseCase({
9
+ createLogtoAdapter,
10
+ sessionStorage,
11
+ });
12
+ await execute({
13
+ cookieHeader: 'abcd',
14
+ callbackUri: '/',
15
+ });
16
+ expect(handleSignInCallback).toBeCalledTimes(1);
17
+ });
18
+ });
@@ -0,0 +1,11 @@
1
+ import type { SessionStorage } from 'react-router';
2
+ import type { CreateLogtoAdapter } from '../../infrastructure/logto/index.js';
3
+ type HandleSignInCallbackDto = {
4
+ readonly redirectBackTo: string;
5
+ };
6
+ type HandleSignInCallbackDeps = {
7
+ readonly createLogtoAdapter: CreateLogtoAdapter;
8
+ readonly sessionStorage: SessionStorage;
9
+ };
10
+ export declare const makeHandleSignInCallback: (dto: HandleSignInCallbackDto, deps: HandleSignInCallbackDeps) => (request: Request) => Promise<Response>;
11
+ export {};
@@ -0,0 +1,17 @@
1
+ import { HandleSignInCallbackController } from './HandleSignInCallbackController.js';
2
+ import { makeHandleSignInCallbackUseCase } from './HandleSignInCallbackUseCase.js';
3
+
4
+ const makeHandleSignInCallback = (dto, deps) => async (request) => {
5
+ const { createLogtoAdapter, sessionStorage } = deps;
6
+ const useCase = makeHandleSignInCallbackUseCase({
7
+ createLogtoAdapter,
8
+ sessionStorage,
9
+ });
10
+ const controller = HandleSignInCallbackController.fromDto({
11
+ useCase,
12
+ redirectUri: dto.redirectBackTo,
13
+ });
14
+ return controller.execute(request);
15
+ };
16
+
17
+ export { makeHandleSignInCallback };
@@ -0,0 +1,14 @@
1
+ import type { HandleSignOutUseCase } from './HandleSignOutUseCase.js';
2
+ type HandleSignOutControllerDto = {
3
+ readonly useCase: HandleSignOutUseCase;
4
+ readonly redirectUri: string;
5
+ };
6
+ export declare class HandleSignOutController {
7
+ private readonly properties;
8
+ static readonly fromDto: (dto: HandleSignOutControllerDto) => HandleSignOutController;
9
+ private readonly useCase;
10
+ private readonly redirectUri;
11
+ private constructor();
12
+ readonly execute: (request: Request) => Promise<Response>;
13
+ }
14
+ export {};
@@ -0,0 +1,32 @@
1
+ import { redirect } from 'react-router';
2
+ import { getCookieHeaderFromRequest } from '../../framework/get-cookie-header-from-request.js';
3
+ import { HandleSignOutError } from './HandleSignOutError.js';
4
+
5
+ class HandleSignOutController {
6
+ static { this.fromDto = (dto) => new HandleSignOutController({
7
+ useCase: dto.useCase,
8
+ redirectUri: dto.redirectUri,
9
+ }); }
10
+ constructor(properties) {
11
+ this.properties = properties;
12
+ this.useCase = this.properties.useCase;
13
+ this.redirectUri = this.properties.redirectUri;
14
+ this.execute = async (request) => {
15
+ const cookieHeader = getCookieHeaderFromRequest(request);
16
+ if (!cookieHeader) {
17
+ throw HandleSignOutError.becauseNoCookieHeaderPresent();
18
+ }
19
+ const result = await this.useCase({
20
+ cookieHeader,
21
+ redirectUri: this.redirectUri,
22
+ });
23
+ return redirect(result.navigateToUrl, {
24
+ headers: {
25
+ 'Set-Cookie': result.cookieHeader,
26
+ },
27
+ });
28
+ };
29
+ }
30
+ }
31
+
32
+ export { HandleSignOutController };