@logto/next 2.1.3 → 2.2.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 +0 -2
- package/lib/edge/index.cjs +35 -20
- package/lib/edge/index.d.ts +2 -1
- package/lib/edge/index.js +35 -20
- package/lib/server-actions/index.cjs +89 -0
- package/lib/server-actions/index.d.ts +44 -0
- package/lib/server-actions/index.js +81 -0
- package/lib/server-actions/index.test.d.ts +1 -0
- package/lib/src/client.cjs +0 -14
- package/lib/src/client.d.ts +2 -13
- package/lib/src/client.js +0 -14
- package/lib/src/index.cjs +62 -17
- package/lib/src/index.d.ts +3 -2
- package/lib/src/index.js +32 -18
- package/lib/src/session.cjs +73 -0
- package/lib/src/session.d.ts +9 -0
- package/lib/src/session.js +69 -0
- package/lib/src/session.test.d.ts +1 -0
- package/lib/src/storage.cjs +9 -0
- package/lib/src/storage.d.ts +11 -7
- package/lib/src/storage.js +9 -0
- package/lib/src/types.d.ts +10 -13
- package/package.json +13 -5
package/README.md
CHANGED
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
The Logto Next.js SDK written in TypeScript. Check out our [integration guide](https://docs.logto.io/docs/recipes/integrate-logto/next-js) or [docs](https://docs.logto.io/sdk/JavaScript/next/) for more information.
|
|
7
7
|
|
|
8
|
-
We also provide [集成指南](https://docs.logto.io/zh-cn/docs/recipes/integrate-logto/next-js/) and [文档](https://docs.logto.io/zh-cn/sdk/JavaScript/next/) in Simplified Chinese.
|
|
9
|
-
|
|
10
8
|
## Installation
|
|
11
9
|
|
|
12
10
|
### Using npm
|
package/lib/edge/index.cjs
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
+
var cookies = require('@edge-runtime/cookies');
|
|
5
6
|
var NodeClient = require('@logto/node/edge');
|
|
6
|
-
var edge = require('iron-session/edge');
|
|
7
7
|
var client = require('../src/client.cjs');
|
|
8
|
+
var session = require('../src/session.cjs');
|
|
8
9
|
|
|
9
10
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
11
|
|
|
@@ -16,41 +17,34 @@ class LogtoClient extends client.default {
|
|
|
16
17
|
NodeClient: NodeClient__default.default,
|
|
17
18
|
});
|
|
18
19
|
this.handleSignIn = (redirectUri = `${this.config.baseUrl}/api/logto/sign-in-callback`, interactionMode) => async (request) => {
|
|
20
|
+
const { nodeClient, headers } = await this.createNodeClientFromEdgeRequest(request);
|
|
21
|
+
await nodeClient.signIn(redirectUri, interactionMode);
|
|
22
|
+
await this.storage?.save();
|
|
19
23
|
const response = new Response(null, {
|
|
24
|
+
headers,
|
|
20
25
|
status: 307,
|
|
21
26
|
});
|
|
22
|
-
const session = await edge.getIronSession(request, response, this.ironSessionConfigs);
|
|
23
|
-
const nodeClient = this.createNodeClient(session);
|
|
24
|
-
await nodeClient.signIn(redirectUri, interactionMode);
|
|
25
|
-
await this.storage?.save();
|
|
26
27
|
if (this.navigateUrl) {
|
|
27
28
|
response.headers.append('Location', this.navigateUrl);
|
|
28
29
|
}
|
|
29
30
|
return response;
|
|
30
31
|
};
|
|
31
32
|
this.handleSignOut = (redirectUri = this.config.baseUrl) => async (request) => {
|
|
33
|
+
const { nodeClient, headers } = await this.createNodeClientFromEdgeRequest(request);
|
|
34
|
+
await nodeClient.signOut(redirectUri);
|
|
35
|
+
await this.storage?.destroy();
|
|
36
|
+
await this.storage?.save();
|
|
32
37
|
const response = new Response(null, {
|
|
38
|
+
headers,
|
|
33
39
|
status: 307,
|
|
34
40
|
});
|
|
35
|
-
const session = await edge.getIronSession(request, response, this.ironSessionConfigs);
|
|
36
|
-
const nodeClient = this.createNodeClient(session);
|
|
37
|
-
await nodeClient.signOut(redirectUri);
|
|
38
|
-
session.destroy();
|
|
39
|
-
await this.storage?.save();
|
|
40
41
|
if (this.navigateUrl) {
|
|
41
42
|
response.headers.append('Location', this.navigateUrl);
|
|
42
43
|
}
|
|
43
44
|
return response;
|
|
44
45
|
};
|
|
45
46
|
this.handleSignInCallback = (redirectTo = this.config.baseUrl) => async (request) => {
|
|
46
|
-
const
|
|
47
|
-
status: 307,
|
|
48
|
-
headers: {
|
|
49
|
-
Location: redirectTo,
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
const session = await edge.getIronSession(request, response, this.ironSessionConfigs);
|
|
53
|
-
const nodeClient = this.createNodeClient(session);
|
|
47
|
+
const { nodeClient, headers } = await this.createNodeClientFromEdgeRequest(request);
|
|
54
48
|
if (request.url) {
|
|
55
49
|
// When app is running behind reverse proxy which is common for edge runtime,
|
|
56
50
|
// the `request.url`'s domain may not be expected, replace to the configured baseUrl
|
|
@@ -59,6 +53,11 @@ class LogtoClient extends client.default {
|
|
|
59
53
|
await nodeClient.handleSignInCallback(callbackUrl.toString());
|
|
60
54
|
await this.storage?.save();
|
|
61
55
|
}
|
|
56
|
+
const response = new Response(null, {
|
|
57
|
+
status: 307,
|
|
58
|
+
headers,
|
|
59
|
+
});
|
|
60
|
+
response.headers.append('Location', redirectTo);
|
|
62
61
|
return response;
|
|
63
62
|
};
|
|
64
63
|
this.handleUser = (configs) => async (request) => {
|
|
@@ -71,11 +70,27 @@ class LogtoClient extends client.default {
|
|
|
71
70
|
});
|
|
72
71
|
};
|
|
73
72
|
this.getLogtoContext = async (request, config = {}) => {
|
|
74
|
-
const
|
|
75
|
-
const context = await
|
|
73
|
+
const { nodeClient } = await this.createNodeClientFromEdgeRequest(request);
|
|
74
|
+
const context = await nodeClient.getContext();
|
|
76
75
|
return context;
|
|
77
76
|
};
|
|
78
77
|
}
|
|
78
|
+
async createNodeClientFromEdgeRequest(request) {
|
|
79
|
+
const cookieName = `logto:${this.config.appId}`;
|
|
80
|
+
const cookies$1 = new cookies.RequestCookies(request.headers);
|
|
81
|
+
const headers = new Headers();
|
|
82
|
+
const responseCookies = new cookies.ResponseCookies(headers);
|
|
83
|
+
const nodeClient = super.createNodeClient(await session.createSession({
|
|
84
|
+
secret: this.config.cookieSecret,
|
|
85
|
+
crypto,
|
|
86
|
+
}, cookies$1.get(cookieName)?.value ?? '', (value) => {
|
|
87
|
+
responseCookies.set(cookieName, value, {
|
|
88
|
+
maxAge: 14 * 3600 * 24,
|
|
89
|
+
secure: this.config.cookieSecure,
|
|
90
|
+
});
|
|
91
|
+
}));
|
|
92
|
+
return { nodeClient, headers };
|
|
93
|
+
}
|
|
79
94
|
}
|
|
80
95
|
|
|
81
96
|
exports.default = LogtoClient;
|
package/lib/edge/index.d.ts
CHANGED
|
@@ -5,9 +5,10 @@ import type { LogtoNextConfig } from '../src/types.js';
|
|
|
5
5
|
export type { LogtoContext, InteractionMode } from '@logto/node';
|
|
6
6
|
export default class LogtoClient extends BaseClient {
|
|
7
7
|
constructor(config: LogtoNextConfig);
|
|
8
|
-
handleSignIn: (redirectUri?: string, interactionMode?: InteractionMode) => (request:
|
|
8
|
+
handleSignIn: (redirectUri?: string, interactionMode?: InteractionMode) => (request: Request) => Promise<Response>;
|
|
9
9
|
handleSignOut: (redirectUri?: string) => (request: NextRequest) => Promise<Response>;
|
|
10
10
|
handleSignInCallback: (redirectTo?: string) => (request: NextRequest) => Promise<Response>;
|
|
11
11
|
handleUser: (configs?: GetContextParameters) => (request: NextRequest) => Promise<Response>;
|
|
12
12
|
getLogtoContext: (request: NextRequest, config?: GetContextParameters) => Promise<import("@logto/node").LogtoContext>;
|
|
13
|
+
private createNodeClientFromEdgeRequest;
|
|
13
14
|
}
|
package/lib/edge/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { RequestCookies, ResponseCookies } from '@edge-runtime/cookies';
|
|
1
2
|
import NodeClient from '@logto/node/edge';
|
|
2
|
-
import { getIronSession } from 'iron-session/edge';
|
|
3
3
|
import LogtoNextBaseClient from '../src/client.js';
|
|
4
|
+
import { createSession } from '../src/session.js';
|
|
4
5
|
|
|
5
6
|
class LogtoClient extends LogtoNextBaseClient {
|
|
6
7
|
constructor(config) {
|
|
@@ -8,41 +9,34 @@ class LogtoClient extends LogtoNextBaseClient {
|
|
|
8
9
|
NodeClient,
|
|
9
10
|
});
|
|
10
11
|
this.handleSignIn = (redirectUri = `${this.config.baseUrl}/api/logto/sign-in-callback`, interactionMode) => async (request) => {
|
|
12
|
+
const { nodeClient, headers } = await this.createNodeClientFromEdgeRequest(request);
|
|
13
|
+
await nodeClient.signIn(redirectUri, interactionMode);
|
|
14
|
+
await this.storage?.save();
|
|
11
15
|
const response = new Response(null, {
|
|
16
|
+
headers,
|
|
12
17
|
status: 307,
|
|
13
18
|
});
|
|
14
|
-
const session = await getIronSession(request, response, this.ironSessionConfigs);
|
|
15
|
-
const nodeClient = this.createNodeClient(session);
|
|
16
|
-
await nodeClient.signIn(redirectUri, interactionMode);
|
|
17
|
-
await this.storage?.save();
|
|
18
19
|
if (this.navigateUrl) {
|
|
19
20
|
response.headers.append('Location', this.navigateUrl);
|
|
20
21
|
}
|
|
21
22
|
return response;
|
|
22
23
|
};
|
|
23
24
|
this.handleSignOut = (redirectUri = this.config.baseUrl) => async (request) => {
|
|
25
|
+
const { nodeClient, headers } = await this.createNodeClientFromEdgeRequest(request);
|
|
26
|
+
await nodeClient.signOut(redirectUri);
|
|
27
|
+
await this.storage?.destroy();
|
|
28
|
+
await this.storage?.save();
|
|
24
29
|
const response = new Response(null, {
|
|
30
|
+
headers,
|
|
25
31
|
status: 307,
|
|
26
32
|
});
|
|
27
|
-
const session = await getIronSession(request, response, this.ironSessionConfigs);
|
|
28
|
-
const nodeClient = this.createNodeClient(session);
|
|
29
|
-
await nodeClient.signOut(redirectUri);
|
|
30
|
-
session.destroy();
|
|
31
|
-
await this.storage?.save();
|
|
32
33
|
if (this.navigateUrl) {
|
|
33
34
|
response.headers.append('Location', this.navigateUrl);
|
|
34
35
|
}
|
|
35
36
|
return response;
|
|
36
37
|
};
|
|
37
38
|
this.handleSignInCallback = (redirectTo = this.config.baseUrl) => async (request) => {
|
|
38
|
-
const
|
|
39
|
-
status: 307,
|
|
40
|
-
headers: {
|
|
41
|
-
Location: redirectTo,
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
const session = await getIronSession(request, response, this.ironSessionConfigs);
|
|
45
|
-
const nodeClient = this.createNodeClient(session);
|
|
39
|
+
const { nodeClient, headers } = await this.createNodeClientFromEdgeRequest(request);
|
|
46
40
|
if (request.url) {
|
|
47
41
|
// When app is running behind reverse proxy which is common for edge runtime,
|
|
48
42
|
// the `request.url`'s domain may not be expected, replace to the configured baseUrl
|
|
@@ -51,6 +45,11 @@ class LogtoClient extends LogtoNextBaseClient {
|
|
|
51
45
|
await nodeClient.handleSignInCallback(callbackUrl.toString());
|
|
52
46
|
await this.storage?.save();
|
|
53
47
|
}
|
|
48
|
+
const response = new Response(null, {
|
|
49
|
+
status: 307,
|
|
50
|
+
headers,
|
|
51
|
+
});
|
|
52
|
+
response.headers.append('Location', redirectTo);
|
|
54
53
|
return response;
|
|
55
54
|
};
|
|
56
55
|
this.handleUser = (configs) => async (request) => {
|
|
@@ -63,11 +62,27 @@ class LogtoClient extends LogtoNextBaseClient {
|
|
|
63
62
|
});
|
|
64
63
|
};
|
|
65
64
|
this.getLogtoContext = async (request, config = {}) => {
|
|
66
|
-
const
|
|
67
|
-
const context = await
|
|
65
|
+
const { nodeClient } = await this.createNodeClientFromEdgeRequest(request);
|
|
66
|
+
const context = await nodeClient.getContext();
|
|
68
67
|
return context;
|
|
69
68
|
};
|
|
70
69
|
}
|
|
70
|
+
async createNodeClientFromEdgeRequest(request) {
|
|
71
|
+
const cookieName = `logto:${this.config.appId}`;
|
|
72
|
+
const cookies = new RequestCookies(request.headers);
|
|
73
|
+
const headers = new Headers();
|
|
74
|
+
const responseCookies = new ResponseCookies(headers);
|
|
75
|
+
const nodeClient = super.createNodeClient(await createSession({
|
|
76
|
+
secret: this.config.cookieSecret,
|
|
77
|
+
crypto,
|
|
78
|
+
}, cookies.get(cookieName)?.value ?? '', (value) => {
|
|
79
|
+
responseCookies.set(cookieName, value, {
|
|
80
|
+
maxAge: 14 * 3600 * 24,
|
|
81
|
+
secure: this.config.cookieSecure,
|
|
82
|
+
});
|
|
83
|
+
}));
|
|
84
|
+
return { nodeClient, headers };
|
|
85
|
+
}
|
|
71
86
|
}
|
|
72
87
|
|
|
73
88
|
export { LogtoClient as default };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var NodeClient = require('@logto/node/edge');
|
|
6
|
+
var client = require('../src/client.cjs');
|
|
7
|
+
var session = require('../src/session.cjs');
|
|
8
|
+
|
|
9
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
+
|
|
11
|
+
var NodeClient__default = /*#__PURE__*/_interopDefault(NodeClient);
|
|
12
|
+
|
|
13
|
+
class LogtoClient extends client.default {
|
|
14
|
+
constructor(config) {
|
|
15
|
+
super(config, {
|
|
16
|
+
NodeClient: NodeClient__default.default,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Init sign-in and return the url to redirect to Logto.
|
|
21
|
+
*
|
|
22
|
+
* @param cookie the raw cookie string
|
|
23
|
+
* @param redirectUri the uri (callbackUri) to redirect to after sign in
|
|
24
|
+
* @param interactionMode OIDC interaction mode
|
|
25
|
+
* @returns the url to redirect to and new cookie if any
|
|
26
|
+
*/
|
|
27
|
+
async handleSignIn(cookie, redirectUri, interactionMode) {
|
|
28
|
+
const { nodeClient, session } = await this.createNodeClientFromHeaders(cookie);
|
|
29
|
+
await nodeClient.signIn(redirectUri, interactionMode);
|
|
30
|
+
if (!this.navigateUrl) {
|
|
31
|
+
// Not expected to happen
|
|
32
|
+
throw new Error('navigateUrl is not set');
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
url: this.navigateUrl,
|
|
36
|
+
newCookie: await session.getValues?.(),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Init sign-out and return the url to redirect to Logto.
|
|
41
|
+
*
|
|
42
|
+
* @param cookie the raw cookie string
|
|
43
|
+
* @param redirectUri the uri (postSignOutUri) to redirect to after sign out
|
|
44
|
+
* @returns the url to redirect to
|
|
45
|
+
*/
|
|
46
|
+
async handleSignOut(cookie, redirectUri = this.config.baseUrl) {
|
|
47
|
+
const { nodeClient } = await this.createNodeClientFromHeaders(cookie);
|
|
48
|
+
await nodeClient.signOut(redirectUri);
|
|
49
|
+
if (!this.navigateUrl) {
|
|
50
|
+
// Not expected to happen
|
|
51
|
+
throw new Error('navigateUrl is not set');
|
|
52
|
+
}
|
|
53
|
+
return this.navigateUrl;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Handle sign-in callback from Logto.
|
|
57
|
+
*
|
|
58
|
+
* @param cookie the raw cookie string
|
|
59
|
+
* @param callbackUrl the uri (callbackUri) to redirect to after sign in, should match the one used in handleSignIn
|
|
60
|
+
* @returns new cookie if any
|
|
61
|
+
*/
|
|
62
|
+
async handleSignInCallback(cookie, callbackUrl) {
|
|
63
|
+
const { nodeClient, session } = await this.createNodeClientFromHeaders(cookie);
|
|
64
|
+
await nodeClient.handleSignInCallback(callbackUrl);
|
|
65
|
+
return session.getValues?.();
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get Logto context from cookies.
|
|
69
|
+
*
|
|
70
|
+
* @param cookie the raw cookie string
|
|
71
|
+
* @param config additional configs of GetContextParameters
|
|
72
|
+
* @returns LogtoContext
|
|
73
|
+
*/
|
|
74
|
+
async getLogtoContext(cookie, config = {}) {
|
|
75
|
+
const { nodeClient } = await this.createNodeClientFromHeaders(cookie);
|
|
76
|
+
const context = await nodeClient.getContext(config);
|
|
77
|
+
return context;
|
|
78
|
+
}
|
|
79
|
+
async createNodeClientFromHeaders(cookie) {
|
|
80
|
+
const session$1 = await session.createSession({
|
|
81
|
+
secret: this.config.cookieSecret,
|
|
82
|
+
crypto,
|
|
83
|
+
}, cookie);
|
|
84
|
+
const nodeClient = super.createNodeClient(session$1);
|
|
85
|
+
return { nodeClient, session: session$1 };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
exports.default = LogtoClient;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type GetContextParameters, type InteractionMode } from '@logto/node';
|
|
2
|
+
import BaseClient from '../src/client';
|
|
3
|
+
import type { LogtoNextConfig } from '../src/types.js';
|
|
4
|
+
export type { LogtoContext, InteractionMode } from '@logto/node';
|
|
5
|
+
export default class LogtoClient extends BaseClient {
|
|
6
|
+
constructor(config: LogtoNextConfig);
|
|
7
|
+
/**
|
|
8
|
+
* Init sign-in and return the url to redirect to Logto.
|
|
9
|
+
*
|
|
10
|
+
* @param cookie the raw cookie string
|
|
11
|
+
* @param redirectUri the uri (callbackUri) to redirect to after sign in
|
|
12
|
+
* @param interactionMode OIDC interaction mode
|
|
13
|
+
* @returns the url to redirect to and new cookie if any
|
|
14
|
+
*/
|
|
15
|
+
handleSignIn(cookie: string, redirectUri: string, interactionMode?: InteractionMode): Promise<{
|
|
16
|
+
url: string;
|
|
17
|
+
newCookie?: string;
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* Init sign-out and return the url to redirect to Logto.
|
|
21
|
+
*
|
|
22
|
+
* @param cookie the raw cookie string
|
|
23
|
+
* @param redirectUri the uri (postSignOutUri) to redirect to after sign out
|
|
24
|
+
* @returns the url to redirect to
|
|
25
|
+
*/
|
|
26
|
+
handleSignOut(cookie: string, redirectUri?: string): Promise<string>;
|
|
27
|
+
/**
|
|
28
|
+
* Handle sign-in callback from Logto.
|
|
29
|
+
*
|
|
30
|
+
* @param cookie the raw cookie string
|
|
31
|
+
* @param callbackUrl the uri (callbackUri) to redirect to after sign in, should match the one used in handleSignIn
|
|
32
|
+
* @returns new cookie if any
|
|
33
|
+
*/
|
|
34
|
+
handleSignInCallback(cookie: string, callbackUrl: string): Promise<string | undefined>;
|
|
35
|
+
/**
|
|
36
|
+
* Get Logto context from cookies.
|
|
37
|
+
*
|
|
38
|
+
* @param cookie the raw cookie string
|
|
39
|
+
* @param config additional configs of GetContextParameters
|
|
40
|
+
* @returns LogtoContext
|
|
41
|
+
*/
|
|
42
|
+
getLogtoContext(cookie: string, config?: GetContextParameters): Promise<import("@logto/node").LogtoContext>;
|
|
43
|
+
private createNodeClientFromHeaders;
|
|
44
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import NodeClient from '@logto/node/edge';
|
|
2
|
+
import LogtoNextBaseClient from '../src/client.js';
|
|
3
|
+
import { createSession } from '../src/session.js';
|
|
4
|
+
|
|
5
|
+
class LogtoClient extends LogtoNextBaseClient {
|
|
6
|
+
constructor(config) {
|
|
7
|
+
super(config, {
|
|
8
|
+
NodeClient,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Init sign-in and return the url to redirect to Logto.
|
|
13
|
+
*
|
|
14
|
+
* @param cookie the raw cookie string
|
|
15
|
+
* @param redirectUri the uri (callbackUri) to redirect to after sign in
|
|
16
|
+
* @param interactionMode OIDC interaction mode
|
|
17
|
+
* @returns the url to redirect to and new cookie if any
|
|
18
|
+
*/
|
|
19
|
+
async handleSignIn(cookie, redirectUri, interactionMode) {
|
|
20
|
+
const { nodeClient, session } = await this.createNodeClientFromHeaders(cookie);
|
|
21
|
+
await nodeClient.signIn(redirectUri, interactionMode);
|
|
22
|
+
if (!this.navigateUrl) {
|
|
23
|
+
// Not expected to happen
|
|
24
|
+
throw new Error('navigateUrl is not set');
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
url: this.navigateUrl,
|
|
28
|
+
newCookie: await session.getValues?.(),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Init sign-out and return the url to redirect to Logto.
|
|
33
|
+
*
|
|
34
|
+
* @param cookie the raw cookie string
|
|
35
|
+
* @param redirectUri the uri (postSignOutUri) to redirect to after sign out
|
|
36
|
+
* @returns the url to redirect to
|
|
37
|
+
*/
|
|
38
|
+
async handleSignOut(cookie, redirectUri = this.config.baseUrl) {
|
|
39
|
+
const { nodeClient } = await this.createNodeClientFromHeaders(cookie);
|
|
40
|
+
await nodeClient.signOut(redirectUri);
|
|
41
|
+
if (!this.navigateUrl) {
|
|
42
|
+
// Not expected to happen
|
|
43
|
+
throw new Error('navigateUrl is not set');
|
|
44
|
+
}
|
|
45
|
+
return this.navigateUrl;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Handle sign-in callback from Logto.
|
|
49
|
+
*
|
|
50
|
+
* @param cookie the raw cookie string
|
|
51
|
+
* @param callbackUrl the uri (callbackUri) to redirect to after sign in, should match the one used in handleSignIn
|
|
52
|
+
* @returns new cookie if any
|
|
53
|
+
*/
|
|
54
|
+
async handleSignInCallback(cookie, callbackUrl) {
|
|
55
|
+
const { nodeClient, session } = await this.createNodeClientFromHeaders(cookie);
|
|
56
|
+
await nodeClient.handleSignInCallback(callbackUrl);
|
|
57
|
+
return session.getValues?.();
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get Logto context from cookies.
|
|
61
|
+
*
|
|
62
|
+
* @param cookie the raw cookie string
|
|
63
|
+
* @param config additional configs of GetContextParameters
|
|
64
|
+
* @returns LogtoContext
|
|
65
|
+
*/
|
|
66
|
+
async getLogtoContext(cookie, config = {}) {
|
|
67
|
+
const { nodeClient } = await this.createNodeClientFromHeaders(cookie);
|
|
68
|
+
const context = await nodeClient.getContext(config);
|
|
69
|
+
return context;
|
|
70
|
+
}
|
|
71
|
+
async createNodeClientFromHeaders(cookie) {
|
|
72
|
+
const session = await createSession({
|
|
73
|
+
secret: this.config.cookieSecret,
|
|
74
|
+
crypto,
|
|
75
|
+
}, cookie);
|
|
76
|
+
const nodeClient = super.createNodeClient(session);
|
|
77
|
+
return { nodeClient, session };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export { LogtoClient as default };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/src/client.cjs
CHANGED
|
@@ -18,20 +18,6 @@ class LogtoNextBaseClient {
|
|
|
18
18
|
},
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
|
-
get ironSessionConfigs() {
|
|
22
|
-
return {
|
|
23
|
-
cookieName: `logto:${this.config.appId}`,
|
|
24
|
-
password: this.config.cookieSecret,
|
|
25
|
-
cookieOptions: {
|
|
26
|
-
secure: this.config.cookieSecure,
|
|
27
|
-
maxAge: 14 * 24 * 60 * 60,
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
async getLogtoUserFromRequest(session, configs) {
|
|
32
|
-
const nodeClient = this.createNodeClient(session);
|
|
33
|
-
return nodeClient.getContext(configs);
|
|
34
|
-
}
|
|
35
21
|
}
|
|
36
22
|
|
|
37
23
|
exports.default = LogtoNextBaseClient;
|
package/lib/src/client.d.ts
CHANGED
|
@@ -1,21 +1,10 @@
|
|
|
1
|
-
import type { GetContextParameters } from '@logto/node';
|
|
2
|
-
import { type IronSession } from 'iron-session';
|
|
3
1
|
import NextStorage from './storage';
|
|
4
|
-
import type { Adapters, LogtoNextConfig } from './types';
|
|
2
|
+
import type { Adapters, LogtoNextConfig, Session } from './types';
|
|
5
3
|
export default class LogtoNextBaseClient {
|
|
6
4
|
protected readonly config: LogtoNextConfig;
|
|
7
5
|
protected readonly adapters: Adapters;
|
|
8
6
|
protected navigateUrl?: string;
|
|
9
7
|
protected storage?: NextStorage;
|
|
10
8
|
constructor(config: LogtoNextConfig, adapters: Adapters);
|
|
11
|
-
protected createNodeClient(session:
|
|
12
|
-
protected get ironSessionConfigs(): {
|
|
13
|
-
cookieName: string;
|
|
14
|
-
password: string;
|
|
15
|
-
cookieOptions: {
|
|
16
|
-
secure: boolean;
|
|
17
|
-
maxAge: number;
|
|
18
|
-
};
|
|
19
|
-
};
|
|
20
|
-
protected getLogtoUserFromRequest(session: IronSession, configs: GetContextParameters): Promise<import("@logto/node").LogtoContext>;
|
|
9
|
+
protected createNodeClient(session: Session): import("@logto/node").default;
|
|
21
10
|
}
|
package/lib/src/client.js
CHANGED
|
@@ -14,20 +14,6 @@ class LogtoNextBaseClient {
|
|
|
14
14
|
},
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
|
-
get ironSessionConfigs() {
|
|
18
|
-
return {
|
|
19
|
-
cookieName: `logto:${this.config.appId}`,
|
|
20
|
-
password: this.config.cookieSecret,
|
|
21
|
-
cookieOptions: {
|
|
22
|
-
secure: this.config.cookieSecure,
|
|
23
|
-
maxAge: 14 * 24 * 60 * 60,
|
|
24
|
-
},
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
async getLogtoUserFromRequest(session, configs) {
|
|
28
|
-
const nodeClient = this.createNodeClient(session);
|
|
29
|
-
return nodeClient.getContext(configs);
|
|
30
|
-
}
|
|
31
17
|
}
|
|
32
18
|
|
|
33
19
|
export { LogtoNextBaseClient as default };
|
package/lib/src/index.cjs
CHANGED
|
@@ -2,12 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
+
var crypto = require('crypto');
|
|
5
6
|
var NodeClient = require('@logto/node');
|
|
6
|
-
var next = require('iron-session/next');
|
|
7
7
|
var client = require('./client.cjs');
|
|
8
|
+
var session = require('./session.cjs');
|
|
8
9
|
|
|
9
10
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
11
|
|
|
12
|
+
function _interopNamespace(e) {
|
|
13
|
+
if (e && e.__esModule) return e;
|
|
14
|
+
var n = Object.create(null);
|
|
15
|
+
if (e) {
|
|
16
|
+
Object.keys(e).forEach(function (k) {
|
|
17
|
+
if (k !== 'default') {
|
|
18
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
19
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
get: function () { return e[k]; }
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
n.default = e;
|
|
27
|
+
return Object.freeze(n);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
|
|
11
31
|
var NodeClient__default = /*#__PURE__*/_interopDefault(NodeClient);
|
|
12
32
|
|
|
13
33
|
class LogtoClient extends client.default {
|
|
@@ -15,31 +35,31 @@ class LogtoClient extends client.default {
|
|
|
15
35
|
super(config, {
|
|
16
36
|
NodeClient: NodeClient__default.default,
|
|
17
37
|
});
|
|
18
|
-
this.handleSignIn = (redirectUri = `${this.config.baseUrl}/api/logto/sign-in-callback`, interactionMode) =>
|
|
19
|
-
const nodeClient = this.
|
|
38
|
+
this.handleSignIn = (redirectUri = `${this.config.baseUrl}/api/logto/sign-in-callback`, interactionMode) => async (request, response) => {
|
|
39
|
+
const nodeClient = await this.createNodeClientFromNextApi(request, response);
|
|
20
40
|
await nodeClient.signIn(redirectUri, interactionMode);
|
|
21
41
|
await this.storage?.save();
|
|
22
42
|
if (this.navigateUrl) {
|
|
23
43
|
response.redirect(this.navigateUrl);
|
|
24
44
|
}
|
|
25
|
-
}
|
|
26
|
-
this.handleSignInCallback = (redirectTo = this.config.baseUrl) =>
|
|
27
|
-
const nodeClient = this.
|
|
45
|
+
};
|
|
46
|
+
this.handleSignInCallback = (redirectTo = this.config.baseUrl) => async (request, response) => {
|
|
47
|
+
const nodeClient = await this.createNodeClientFromNextApi(request, response);
|
|
28
48
|
if (request.url) {
|
|
29
49
|
await nodeClient.handleSignInCallback(`${this.config.baseUrl}${request.url}`);
|
|
30
50
|
await this.storage?.save();
|
|
31
51
|
response.redirect(redirectTo);
|
|
32
52
|
}
|
|
33
|
-
}
|
|
34
|
-
this.handleSignOut = (redirectUri = this.config.baseUrl) =>
|
|
35
|
-
const nodeClient = this.
|
|
53
|
+
};
|
|
54
|
+
this.handleSignOut = (redirectUri = this.config.baseUrl) => async (request, response) => {
|
|
55
|
+
const nodeClient = await this.createNodeClientFromNextApi(request, response);
|
|
36
56
|
await nodeClient.signOut(redirectUri);
|
|
37
|
-
|
|
57
|
+
await this.storage?.destroy();
|
|
38
58
|
await this.storage?.save();
|
|
39
59
|
if (this.navigateUrl) {
|
|
40
60
|
response.redirect(this.navigateUrl);
|
|
41
61
|
}
|
|
42
|
-
}
|
|
62
|
+
};
|
|
43
63
|
this.handleUser = (configs) => this.withLogtoApiRoute((request, response) => {
|
|
44
64
|
response.json(request.user);
|
|
45
65
|
}, configs);
|
|
@@ -62,21 +82,46 @@ class LogtoClient extends client.default {
|
|
|
62
82
|
}
|
|
63
83
|
response.status(404).end();
|
|
64
84
|
};
|
|
65
|
-
this.withLogtoApiRoute = (handler, config = {}) =>
|
|
66
|
-
const
|
|
85
|
+
this.withLogtoApiRoute = (handler, config = {}) => async (request, response) => {
|
|
86
|
+
const nodeClient = await this.createNodeClientFromNextApi(request, response);
|
|
87
|
+
const user = await nodeClient.getContext(config);
|
|
67
88
|
// eslint-disable-next-line @silverhand/fp/no-mutating-methods
|
|
68
89
|
Object.defineProperty(request, 'user', { enumerable: true, get: () => user });
|
|
69
90
|
return handler(request, response);
|
|
70
|
-
}
|
|
71
|
-
this.withLogtoSsr = (handler, configs = {}) =>
|
|
72
|
-
const
|
|
91
|
+
};
|
|
92
|
+
this.withLogtoSsr = (handler, configs = {}) => async (context) => {
|
|
93
|
+
const nodeClient = await this.createNodeClientFromNextApi(context.req, context.res);
|
|
94
|
+
const user = await nodeClient.getContext(configs);
|
|
73
95
|
// eslint-disable-next-line @silverhand/fp/no-mutating-methods
|
|
74
96
|
Object.defineProperty(context.req, 'user', { enumerable: true, get: () => user });
|
|
75
97
|
return handler(context);
|
|
76
|
-
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
async createNodeClientFromNextApi(request, response) {
|
|
101
|
+
const cookieName = `logto:${this.config.appId}`;
|
|
102
|
+
return super.createNodeClient(await session.createSession({
|
|
103
|
+
secret: this.config.cookieSecret,
|
|
104
|
+
crypto: crypto__namespace,
|
|
105
|
+
}, request.cookies[cookieName] ?? '', (value) => {
|
|
106
|
+
const secure = this.config.cookieSecure;
|
|
107
|
+
const maxAge = 14 * 3600 * 24;
|
|
108
|
+
response.setHeader('Set-Cookie', `${cookieName}=${value}; Path=/; Max-Age=${maxAge}; ${secure ? 'Secure; SameSite=None' : ''}`);
|
|
109
|
+
}));
|
|
77
110
|
}
|
|
78
111
|
}
|
|
79
112
|
|
|
113
|
+
Object.defineProperty(exports, 'LogtoClientError', {
|
|
114
|
+
enumerable: true,
|
|
115
|
+
get: function () { return NodeClient.LogtoClientError; }
|
|
116
|
+
});
|
|
117
|
+
Object.defineProperty(exports, 'LogtoError', {
|
|
118
|
+
enumerable: true,
|
|
119
|
+
get: function () { return NodeClient.LogtoError; }
|
|
120
|
+
});
|
|
121
|
+
Object.defineProperty(exports, 'LogtoRequestError', {
|
|
122
|
+
enumerable: true,
|
|
123
|
+
get: function () { return NodeClient.LogtoRequestError; }
|
|
124
|
+
});
|
|
80
125
|
Object.defineProperty(exports, 'ReservedScope', {
|
|
81
126
|
enumerable: true,
|
|
82
127
|
get: function () { return NodeClient.ReservedScope; }
|
package/lib/src/index.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { type GetContextParameters, type InteractionMode } from '@logto/node';
|
|
|
2
2
|
import { type GetServerSidePropsResult, type GetServerSidePropsContext, type NextApiHandler } from 'next';
|
|
3
3
|
import LogtoNextBaseClient from './client.js';
|
|
4
4
|
import type { LogtoNextConfig } from './types.js';
|
|
5
|
-
export { ReservedScope, UserScope } from '@logto/node';
|
|
6
|
-
export type { LogtoContext, InteractionMode } from '@logto/node';
|
|
5
|
+
export { ReservedScope, UserScope, LogtoError, LogtoClientError, LogtoRequestError, } from '@logto/node';
|
|
6
|
+
export type { LogtoContext, InteractionMode, LogtoErrorCode } from '@logto/node';
|
|
7
7
|
export default class LogtoClient extends LogtoNextBaseClient {
|
|
8
8
|
constructor(config: LogtoNextConfig);
|
|
9
9
|
handleSignIn: (redirectUri?: string, interactionMode?: InteractionMode) => NextApiHandler;
|
|
@@ -13,4 +13,5 @@ export default class LogtoClient extends LogtoNextBaseClient {
|
|
|
13
13
|
handleAuthRoutes: (configs?: GetContextParameters) => NextApiHandler;
|
|
14
14
|
withLogtoApiRoute: (handler: NextApiHandler, config?: GetContextParameters) => NextApiHandler;
|
|
15
15
|
withLogtoSsr: <P extends Record<string, unknown> = Record<string, unknown>>(handler: (context: GetServerSidePropsContext) => GetServerSidePropsResult<P> | Promise<GetServerSidePropsResult<P>>, configs?: GetContextParameters) => (context: GetServerSidePropsContext) => Promise<GetServerSidePropsResult<P>>;
|
|
16
|
+
private createNodeClientFromNextApi;
|
|
16
17
|
}
|
package/lib/src/index.js
CHANGED
|
@@ -1,38 +1,39 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
1
2
|
import NodeClient from '@logto/node';
|
|
2
|
-
export { ReservedScope, UserScope } from '@logto/node';
|
|
3
|
-
import { withIronSessionApiRoute, withIronSessionSsr } from 'iron-session/next';
|
|
3
|
+
export { LogtoClientError, LogtoError, LogtoRequestError, ReservedScope, UserScope } from '@logto/node';
|
|
4
4
|
import LogtoNextBaseClient from './client.js';
|
|
5
|
+
import { createSession } from './session.js';
|
|
5
6
|
|
|
6
7
|
class LogtoClient extends LogtoNextBaseClient {
|
|
7
8
|
constructor(config) {
|
|
8
9
|
super(config, {
|
|
9
10
|
NodeClient,
|
|
10
11
|
});
|
|
11
|
-
this.handleSignIn = (redirectUri = `${this.config.baseUrl}/api/logto/sign-in-callback`, interactionMode) =>
|
|
12
|
-
const nodeClient = this.
|
|
12
|
+
this.handleSignIn = (redirectUri = `${this.config.baseUrl}/api/logto/sign-in-callback`, interactionMode) => async (request, response) => {
|
|
13
|
+
const nodeClient = await this.createNodeClientFromNextApi(request, response);
|
|
13
14
|
await nodeClient.signIn(redirectUri, interactionMode);
|
|
14
15
|
await this.storage?.save();
|
|
15
16
|
if (this.navigateUrl) {
|
|
16
17
|
response.redirect(this.navigateUrl);
|
|
17
18
|
}
|
|
18
|
-
}
|
|
19
|
-
this.handleSignInCallback = (redirectTo = this.config.baseUrl) =>
|
|
20
|
-
const nodeClient = this.
|
|
19
|
+
};
|
|
20
|
+
this.handleSignInCallback = (redirectTo = this.config.baseUrl) => async (request, response) => {
|
|
21
|
+
const nodeClient = await this.createNodeClientFromNextApi(request, response);
|
|
21
22
|
if (request.url) {
|
|
22
23
|
await nodeClient.handleSignInCallback(`${this.config.baseUrl}${request.url}`);
|
|
23
24
|
await this.storage?.save();
|
|
24
25
|
response.redirect(redirectTo);
|
|
25
26
|
}
|
|
26
|
-
}
|
|
27
|
-
this.handleSignOut = (redirectUri = this.config.baseUrl) =>
|
|
28
|
-
const nodeClient = this.
|
|
27
|
+
};
|
|
28
|
+
this.handleSignOut = (redirectUri = this.config.baseUrl) => async (request, response) => {
|
|
29
|
+
const nodeClient = await this.createNodeClientFromNextApi(request, response);
|
|
29
30
|
await nodeClient.signOut(redirectUri);
|
|
30
|
-
|
|
31
|
+
await this.storage?.destroy();
|
|
31
32
|
await this.storage?.save();
|
|
32
33
|
if (this.navigateUrl) {
|
|
33
34
|
response.redirect(this.navigateUrl);
|
|
34
35
|
}
|
|
35
|
-
}
|
|
36
|
+
};
|
|
36
37
|
this.handleUser = (configs) => this.withLogtoApiRoute((request, response) => {
|
|
37
38
|
response.json(request.user);
|
|
38
39
|
}, configs);
|
|
@@ -55,18 +56,31 @@ class LogtoClient extends LogtoNextBaseClient {
|
|
|
55
56
|
}
|
|
56
57
|
response.status(404).end();
|
|
57
58
|
};
|
|
58
|
-
this.withLogtoApiRoute = (handler, config = {}) =>
|
|
59
|
-
const
|
|
59
|
+
this.withLogtoApiRoute = (handler, config = {}) => async (request, response) => {
|
|
60
|
+
const nodeClient = await this.createNodeClientFromNextApi(request, response);
|
|
61
|
+
const user = await nodeClient.getContext(config);
|
|
60
62
|
// eslint-disable-next-line @silverhand/fp/no-mutating-methods
|
|
61
63
|
Object.defineProperty(request, 'user', { enumerable: true, get: () => user });
|
|
62
64
|
return handler(request, response);
|
|
63
|
-
}
|
|
64
|
-
this.withLogtoSsr = (handler, configs = {}) =>
|
|
65
|
-
const
|
|
65
|
+
};
|
|
66
|
+
this.withLogtoSsr = (handler, configs = {}) => async (context) => {
|
|
67
|
+
const nodeClient = await this.createNodeClientFromNextApi(context.req, context.res);
|
|
68
|
+
const user = await nodeClient.getContext(configs);
|
|
66
69
|
// eslint-disable-next-line @silverhand/fp/no-mutating-methods
|
|
67
70
|
Object.defineProperty(context.req, 'user', { enumerable: true, get: () => user });
|
|
68
71
|
return handler(context);
|
|
69
|
-
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async createNodeClientFromNextApi(request, response) {
|
|
75
|
+
const cookieName = `logto:${this.config.appId}`;
|
|
76
|
+
return super.createNodeClient(await createSession({
|
|
77
|
+
secret: this.config.cookieSecret,
|
|
78
|
+
crypto,
|
|
79
|
+
}, request.cookies[cookieName] ?? '', (value) => {
|
|
80
|
+
const secure = this.config.cookieSecure;
|
|
81
|
+
const maxAge = 14 * 3600 * 24;
|
|
82
|
+
response.setHeader('Set-Cookie', `${cookieName}=${value}; Path=/; Max-Age=${maxAge}; ${secure ? 'Secure; SameSite=None' : ''}`);
|
|
83
|
+
}));
|
|
70
84
|
}
|
|
71
85
|
}
|
|
72
86
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
async function getKeyFromPassword(password, crypto) {
|
|
4
|
+
const encoder = new TextEncoder();
|
|
5
|
+
const data = encoder.encode(password);
|
|
6
|
+
const hash = await crypto.subtle.digest('SHA-256', data);
|
|
7
|
+
// Convert the hash to a hex string
|
|
8
|
+
return Array.from(new Uint8Array(hash))
|
|
9
|
+
.map((byte) => byte.toString(16).padStart(2, '0'))
|
|
10
|
+
.join('');
|
|
11
|
+
}
|
|
12
|
+
async function encrypt(text, password, crypto) {
|
|
13
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
14
|
+
const encodedPlaintext = new TextEncoder().encode(text);
|
|
15
|
+
const secretKey = await crypto.subtle.importKey('raw', Buffer.from(await getKeyFromPassword(password, crypto), 'hex'), {
|
|
16
|
+
name: 'AES-GCM',
|
|
17
|
+
length: 256,
|
|
18
|
+
}, true, ['encrypt', 'decrypt']);
|
|
19
|
+
const ciphertext = await crypto.subtle.encrypt({
|
|
20
|
+
name: 'AES-GCM',
|
|
21
|
+
iv,
|
|
22
|
+
}, secretKey, encodedPlaintext);
|
|
23
|
+
return {
|
|
24
|
+
ciphertext: Buffer.from(ciphertext).toString('base64'),
|
|
25
|
+
iv: Buffer.from(iv).toString('base64'),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
async function decrypt(ciphertext, iv, password, crypto) {
|
|
29
|
+
const secretKey = await crypto.subtle.importKey('raw', Buffer.from(await getKeyFromPassword(password, crypto), 'hex'), {
|
|
30
|
+
name: 'AES-GCM',
|
|
31
|
+
length: 256,
|
|
32
|
+
}, true, ['encrypt', 'decrypt']);
|
|
33
|
+
const cleartext = await crypto.subtle.decrypt({
|
|
34
|
+
name: 'AES-GCM',
|
|
35
|
+
iv: Buffer.from(iv, 'base64'),
|
|
36
|
+
}, secretKey, Buffer.from(ciphertext, 'base64'));
|
|
37
|
+
return new TextDecoder().decode(cleartext);
|
|
38
|
+
}
|
|
39
|
+
const unwrapSession = async (cookie, secret, crypto) => {
|
|
40
|
+
try {
|
|
41
|
+
const [ciphertext, iv] = cookie.split('.');
|
|
42
|
+
if (!ciphertext || !iv) {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
const decrypted = await decrypt(ciphertext, iv, secret, crypto);
|
|
46
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
47
|
+
return JSON.parse(decrypted);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Ignore invalid session
|
|
51
|
+
}
|
|
52
|
+
return {};
|
|
53
|
+
};
|
|
54
|
+
const wrapSession = async (session, secret, crypto) => {
|
|
55
|
+
const { ciphertext, iv } = await encrypt(JSON.stringify(session), secret, crypto);
|
|
56
|
+
return `${ciphertext}.${iv}`;
|
|
57
|
+
};
|
|
58
|
+
const createSession = async ({ secret, crypto }, cookie, setCookie) => {
|
|
59
|
+
const data = await unwrapSession(cookie, secret, crypto);
|
|
60
|
+
const getValues = async () => wrapSession(session, secret, crypto);
|
|
61
|
+
const session = {
|
|
62
|
+
...data,
|
|
63
|
+
save: async () => {
|
|
64
|
+
setCookie?.(await getValues());
|
|
65
|
+
},
|
|
66
|
+
getValues,
|
|
67
|
+
};
|
|
68
|
+
return session;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
exports.createSession = createSession;
|
|
72
|
+
exports.unwrapSession = unwrapSession;
|
|
73
|
+
exports.wrapSession = wrapSession;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type SessionData, type Session } from './types';
|
|
2
|
+
export declare const unwrapSession: (cookie: string, secret: string, crypto: Crypto) => Promise<SessionData>;
|
|
3
|
+
export declare const wrapSession: (session: SessionData, secret: string, crypto: Crypto) => Promise<string>;
|
|
4
|
+
type SessionConfigs = {
|
|
5
|
+
secret: string;
|
|
6
|
+
crypto: Crypto;
|
|
7
|
+
};
|
|
8
|
+
export declare const createSession: ({ secret, crypto }: SessionConfigs, cookie: string, setCookie?: ((value: string) => void) | undefined) => Promise<Session>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
async function getKeyFromPassword(password, crypto) {
|
|
2
|
+
const encoder = new TextEncoder();
|
|
3
|
+
const data = encoder.encode(password);
|
|
4
|
+
const hash = await crypto.subtle.digest('SHA-256', data);
|
|
5
|
+
// Convert the hash to a hex string
|
|
6
|
+
return Array.from(new Uint8Array(hash))
|
|
7
|
+
.map((byte) => byte.toString(16).padStart(2, '0'))
|
|
8
|
+
.join('');
|
|
9
|
+
}
|
|
10
|
+
async function encrypt(text, password, crypto) {
|
|
11
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
12
|
+
const encodedPlaintext = new TextEncoder().encode(text);
|
|
13
|
+
const secretKey = await crypto.subtle.importKey('raw', Buffer.from(await getKeyFromPassword(password, crypto), 'hex'), {
|
|
14
|
+
name: 'AES-GCM',
|
|
15
|
+
length: 256,
|
|
16
|
+
}, true, ['encrypt', 'decrypt']);
|
|
17
|
+
const ciphertext = await crypto.subtle.encrypt({
|
|
18
|
+
name: 'AES-GCM',
|
|
19
|
+
iv,
|
|
20
|
+
}, secretKey, encodedPlaintext);
|
|
21
|
+
return {
|
|
22
|
+
ciphertext: Buffer.from(ciphertext).toString('base64'),
|
|
23
|
+
iv: Buffer.from(iv).toString('base64'),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
async function decrypt(ciphertext, iv, password, crypto) {
|
|
27
|
+
const secretKey = await crypto.subtle.importKey('raw', Buffer.from(await getKeyFromPassword(password, crypto), 'hex'), {
|
|
28
|
+
name: 'AES-GCM',
|
|
29
|
+
length: 256,
|
|
30
|
+
}, true, ['encrypt', 'decrypt']);
|
|
31
|
+
const cleartext = await crypto.subtle.decrypt({
|
|
32
|
+
name: 'AES-GCM',
|
|
33
|
+
iv: Buffer.from(iv, 'base64'),
|
|
34
|
+
}, secretKey, Buffer.from(ciphertext, 'base64'));
|
|
35
|
+
return new TextDecoder().decode(cleartext);
|
|
36
|
+
}
|
|
37
|
+
const unwrapSession = async (cookie, secret, crypto) => {
|
|
38
|
+
try {
|
|
39
|
+
const [ciphertext, iv] = cookie.split('.');
|
|
40
|
+
if (!ciphertext || !iv) {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
const decrypted = await decrypt(ciphertext, iv, secret, crypto);
|
|
44
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
45
|
+
return JSON.parse(decrypted);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Ignore invalid session
|
|
49
|
+
}
|
|
50
|
+
return {};
|
|
51
|
+
};
|
|
52
|
+
const wrapSession = async (session, secret, crypto) => {
|
|
53
|
+
const { ciphertext, iv } = await encrypt(JSON.stringify(session), secret, crypto);
|
|
54
|
+
return `${ciphertext}.${iv}`;
|
|
55
|
+
};
|
|
56
|
+
const createSession = async ({ secret, crypto }, cookie, setCookie) => {
|
|
57
|
+
const data = await unwrapSession(cookie, secret, crypto);
|
|
58
|
+
const getValues = async () => wrapSession(session, secret, crypto);
|
|
59
|
+
const session = {
|
|
60
|
+
...data,
|
|
61
|
+
save: async () => {
|
|
62
|
+
setCookie?.(await getValues());
|
|
63
|
+
},
|
|
64
|
+
getValues,
|
|
65
|
+
};
|
|
66
|
+
return session;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export { createSession, unwrapSession, wrapSession };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/src/storage.cjs
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
+
var NodeClient = require('@logto/node/edge');
|
|
6
|
+
|
|
5
7
|
class NextStorage {
|
|
6
8
|
constructor(session) {
|
|
7
9
|
this.session = session;
|
|
@@ -22,6 +24,13 @@ class NextStorage {
|
|
|
22
24
|
this.session[key] = undefined;
|
|
23
25
|
this.sessionChanged = true;
|
|
24
26
|
}
|
|
27
|
+
async destroy() {
|
|
28
|
+
this.session[NodeClient.PersistKey.AccessToken] = undefined;
|
|
29
|
+
this.session[NodeClient.PersistKey.IdToken] = undefined;
|
|
30
|
+
this.session[NodeClient.PersistKey.SignInSession] = undefined;
|
|
31
|
+
this.session[NodeClient.PersistKey.RefreshToken] = undefined;
|
|
32
|
+
this.sessionChanged = true;
|
|
33
|
+
}
|
|
25
34
|
async save() {
|
|
26
35
|
if (!this.sessionChanged) {
|
|
27
36
|
return;
|
package/lib/src/storage.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { type Storage } from '@logto/node';
|
|
2
|
+
import { PersistKey } from '@logto/node/edge';
|
|
3
|
+
import { type Session } from './types';
|
|
4
|
+
export default class NextStorage implements Storage<PersistKey> {
|
|
4
5
|
private readonly session;
|
|
5
6
|
private sessionChanged;
|
|
6
|
-
constructor(session:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
constructor(session: Session & {
|
|
8
|
+
save: () => Promise<void>;
|
|
9
|
+
});
|
|
10
|
+
setItem(key: PersistKey, value: string): Promise<void>;
|
|
11
|
+
getItem(key: PersistKey): Promise<string | null>;
|
|
12
|
+
removeItem(key: PersistKey): Promise<void>;
|
|
13
|
+
destroy(): Promise<void>;
|
|
10
14
|
save(): Promise<void>;
|
|
11
15
|
}
|
package/lib/src/storage.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { PersistKey } from '@logto/node/edge';
|
|
2
|
+
|
|
1
3
|
class NextStorage {
|
|
2
4
|
constructor(session) {
|
|
3
5
|
this.session = session;
|
|
@@ -18,6 +20,13 @@ class NextStorage {
|
|
|
18
20
|
this.session[key] = undefined;
|
|
19
21
|
this.sessionChanged = true;
|
|
20
22
|
}
|
|
23
|
+
async destroy() {
|
|
24
|
+
this.session[PersistKey.AccessToken] = undefined;
|
|
25
|
+
this.session[PersistKey.IdToken] = undefined;
|
|
26
|
+
this.session[PersistKey.SignInSession] = undefined;
|
|
27
|
+
this.session[PersistKey.RefreshToken] = undefined;
|
|
28
|
+
this.sessionChanged = true;
|
|
29
|
+
}
|
|
21
30
|
async save() {
|
|
22
31
|
if (!this.sessionChanged) {
|
|
23
32
|
return;
|
package/lib/src/types.d.ts
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
import type { LogtoConfig } from '@logto/node';
|
|
1
|
+
import type { LogtoConfig, PersistKey } from '@logto/node';
|
|
2
2
|
import type NodeClient from '@logto/node';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
export type SessionData = {
|
|
4
|
+
[PersistKey.AccessToken]?: string;
|
|
5
|
+
[PersistKey.IdToken]?: string;
|
|
6
|
+
[PersistKey.SignInSession]?: string;
|
|
7
|
+
[PersistKey.RefreshToken]?: string;
|
|
8
|
+
};
|
|
9
|
+
export type Session = SessionData & {
|
|
10
|
+
save: () => Promise<void>;
|
|
11
|
+
getValues?: () => Promise<string>;
|
|
7
12
|
};
|
|
8
|
-
declare module 'iron-session' {
|
|
9
|
-
interface IronSessionData {
|
|
10
|
-
accessToken?: string;
|
|
11
|
-
idToken?: string;
|
|
12
|
-
signInSession?: string;
|
|
13
|
-
refreshToken?: string;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
13
|
export type LogtoNextConfig = LogtoConfig & {
|
|
17
14
|
cookieSecret: string;
|
|
18
15
|
cookieSecure: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logto/next",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./lib/src/index.cjs",
|
|
6
6
|
"module": "./lib/src/index.js",
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
],
|
|
13
13
|
"edge": [
|
|
14
14
|
"./lib/edge/index.d.ts"
|
|
15
|
+
],
|
|
16
|
+
"server-actions": [
|
|
17
|
+
"./lib/server-actions/index.d.ts"
|
|
15
18
|
]
|
|
16
19
|
}
|
|
17
20
|
},
|
|
@@ -25,6 +28,11 @@
|
|
|
25
28
|
"require": "./lib/edge/index.cjs",
|
|
26
29
|
"import": "./lib/edge/index.js",
|
|
27
30
|
"types": "./lib/edge/index.d.ts"
|
|
31
|
+
},
|
|
32
|
+
"./server-actions": {
|
|
33
|
+
"require": "./lib/server-actions/index.cjs",
|
|
34
|
+
"import": "./lib/server-actions/index.js",
|
|
35
|
+
"types": "./lib/server-actions/index.d.ts"
|
|
28
36
|
}
|
|
29
37
|
},
|
|
30
38
|
"files": [
|
|
@@ -37,8 +45,8 @@
|
|
|
37
45
|
"directory": "packages/next"
|
|
38
46
|
},
|
|
39
47
|
"dependencies": {
|
|
40
|
-
"@
|
|
41
|
-
"
|
|
48
|
+
"@edge-runtime/cookies": "^4.0.0",
|
|
49
|
+
"@logto/node": "^2.1.2"
|
|
42
50
|
},
|
|
43
51
|
"devDependencies": {
|
|
44
52
|
"@silverhand/eslint-config": "^4.0.1",
|
|
@@ -49,10 +57,10 @@
|
|
|
49
57
|
"@types/jest": "^29.5.0",
|
|
50
58
|
"eslint": "^8.44.0",
|
|
51
59
|
"jest": "^29.5.0",
|
|
52
|
-
"jest-location-mock": "^
|
|
60
|
+
"jest-location-mock": "^2.0.0",
|
|
53
61
|
"jest-matcher-specific-error": "^1.0.0",
|
|
54
62
|
"lint-staged": "^14.0.0",
|
|
55
|
-
"next": "^13.
|
|
63
|
+
"next": "^13.5.3",
|
|
56
64
|
"next-test-api-route-handler": "^3.1.6",
|
|
57
65
|
"prettier": "^3.0.0",
|
|
58
66
|
"react": "^18.2.0",
|