@workos-inc/authkit-nextjs 0.4.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 +145 -0
- package/dist/cjs/auth.d.ts +3 -0
- package/dist/cjs/auth.js +17 -0
- package/dist/cjs/auth.js.map +1 -0
- package/dist/cjs/authkit-callback-route.d.ts +3 -0
- package/dist/cjs/authkit-callback-route.js +58 -0
- package/dist/cjs/authkit-callback-route.js.map +1 -0
- package/dist/cjs/button.d.ts +3 -0
- package/dist/cjs/button.js +25 -0
- package/dist/cjs/button.js.map +1 -0
- package/dist/cjs/cookie.d.ts +8 -0
- package/dist/cjs/cookie.js +13 -0
- package/dist/cjs/cookie.js.map +1 -0
- package/dist/cjs/env-variables.d.ts +5 -0
- package/dist/cjs/env-variables.js +22 -0
- package/dist/cjs/env-variables.js.map +1 -0
- package/dist/cjs/get-authorization-url.d.ts +2 -0
- package/dist/cjs/get-authorization-url.js +15 -0
- package/dist/cjs/get-authorization-url.js.map +1 -0
- package/dist/cjs/impersonation.d.ts +6 -0
- package/dist/cjs/impersonation.js +119 -0
- package/dist/cjs/impersonation.js.map +1 -0
- package/dist/cjs/index.d.ts +6 -0
- package/dist/cjs/index.js +15 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/interfaces.d.ts +33 -0
- package/dist/cjs/interfaces.js +3 -0
- package/dist/cjs/interfaces.js.map +1 -0
- package/dist/cjs/middleware.d.ts +6 -0
- package/dist/cjs/middleware.js +11 -0
- package/dist/cjs/middleware.js.map +1 -0
- package/dist/cjs/min-max-button.d.ts +7 -0
- package/dist/cjs/min-max-button.js +15 -0
- package/dist/cjs/min-max-button.js.map +1 -0
- package/dist/cjs/session.d.ts +12 -0
- package/dist/cjs/session.js +123 -0
- package/dist/cjs/session.js.map +1 -0
- package/dist/cjs/workos.d.ts +3 -0
- package/dist/cjs/workos.js +10 -0
- package/dist/cjs/workos.js.map +1 -0
- package/package.json +55 -0
- package/src/auth.ts +15 -0
- package/src/authkit-callback-route.ts +69 -0
- package/src/button.tsx +32 -0
- package/src/cookie.ts +9 -0
- package/src/env-variables.ts +18 -0
- package/src/get-authorization-url.ts +13 -0
- package/src/impersonation.tsx +157 -0
- package/src/index.ts +17 -0
- package/src/interfaces.ts +37 -0
- package/src/middleware.ts +12 -0
- package/src/min-max-button.tsx +23 -0
- package/src/session.ts +137 -0
- package/src/workos.ts +7 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
'use client';
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.MinMaxButton = void 0;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const React = tslib_1.__importStar(require("react"));
|
|
7
|
+
const button_js_1 = require("./button.js");
|
|
8
|
+
function MinMaxButton({ children, minimizedValue }) {
|
|
9
|
+
return (React.createElement(button_js_1.Button, { onClick: () => {
|
|
10
|
+
const root = document.querySelector('[data-workos-impersonation-root]');
|
|
11
|
+
root === null || root === void 0 ? void 0 : root.style.setProperty('--wi-minimized', minimizedValue);
|
|
12
|
+
}, style: { padding: 0, width: '1.714em' } }, children));
|
|
13
|
+
}
|
|
14
|
+
exports.MinMaxButton = MinMaxButton;
|
|
15
|
+
//# sourceMappingURL=min-max-button.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"min-max-button.js","sourceRoot":"","sources":["../../src/min-max-button.tsx"],"names":[],"mappings":";AAAA,YAAY,CAAC;;;;AAEb,qDAA+B;AAC/B,2CAAqC;AAOrC,SAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,cAAc,EAAqB;IAC1E,OAAO,CACL,oBAAC,kBAAM,IACL,OAAO,EAAE,GAAG,EAAE;YACZ,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,kCAAkC,CAAuB,CAAC;YAC9F,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,KAAK,CAAC,WAAW,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAAC;QAC5D,CAAC,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,IAEtC,QAAQ,CACF,CACV,CAAC;AACJ,CAAC;AAZD,oCAYC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { NoUserInfo, Session, UserInfo } from './interfaces.js';
|
|
3
|
+
declare function encryptSession(session: Session): Promise<string>;
|
|
4
|
+
declare function updateSession(request: NextRequest, debug: boolean): Promise<NextResponse<unknown>>;
|
|
5
|
+
declare function getUser(options?: {
|
|
6
|
+
ensureSignedIn: false;
|
|
7
|
+
}): Promise<UserInfo | NoUserInfo>;
|
|
8
|
+
declare function getUser(options: {
|
|
9
|
+
ensureSignedIn: true;
|
|
10
|
+
}): Promise<UserInfo>;
|
|
11
|
+
declare function terminateSession(): Promise<void>;
|
|
12
|
+
export { encryptSession, updateSession, getUser, terminateSession };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.terminateSession = exports.getUser = exports.updateSession = exports.encryptSession = void 0;
|
|
4
|
+
const navigation_1 = require("next/navigation");
|
|
5
|
+
const headers_1 = require("next/headers");
|
|
6
|
+
const server_1 = require("next/server");
|
|
7
|
+
const jose_1 = require("jose");
|
|
8
|
+
const iron_session_1 = require("iron-session");
|
|
9
|
+
const cookie_js_1 = require("./cookie.js");
|
|
10
|
+
const workos_js_1 = require("./workos.js");
|
|
11
|
+
const env_variables_js_1 = require("./env-variables.js");
|
|
12
|
+
const get_authorization_url_js_1 = require("./get-authorization-url.js");
|
|
13
|
+
const sessionHeaderName = 'x-workos-session';
|
|
14
|
+
const JWKS = (0, jose_1.createRemoteJWKSet)(new URL(workos_js_1.workos.userManagement.getJwksUrl(env_variables_js_1.WORKOS_CLIENT_ID)));
|
|
15
|
+
async function encryptSession(session) {
|
|
16
|
+
return (0, iron_session_1.sealData)(session, { password: env_variables_js_1.WORKOS_COOKIE_PASSWORD });
|
|
17
|
+
}
|
|
18
|
+
exports.encryptSession = encryptSession;
|
|
19
|
+
async function updateSession(request, debug) {
|
|
20
|
+
const session = await getSessionFromCookie();
|
|
21
|
+
// If no session, just continue
|
|
22
|
+
if (!session) {
|
|
23
|
+
return server_1.NextResponse.next();
|
|
24
|
+
}
|
|
25
|
+
const hasValidSession = await verifyAccessToken(session.accessToken);
|
|
26
|
+
const newRequestHeaders = new Headers(request.headers);
|
|
27
|
+
if (hasValidSession) {
|
|
28
|
+
if (debug)
|
|
29
|
+
console.log('Session is valid');
|
|
30
|
+
// set the x-workos-session header according to the current cookie value
|
|
31
|
+
newRequestHeaders.set(sessionHeaderName, (0, headers_1.cookies)().get(cookie_js_1.cookieName).value);
|
|
32
|
+
return server_1.NextResponse.next({
|
|
33
|
+
headers: newRequestHeaders,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
if (debug)
|
|
38
|
+
console.log('Session invalid. Attempting refresh', session.refreshToken);
|
|
39
|
+
// If the session is invalid (i.e. the access token has expired) attempt to re-authenticate with the refresh token
|
|
40
|
+
const { accessToken, refreshToken } = await workos_js_1.workos.userManagement.authenticateWithRefreshToken({
|
|
41
|
+
clientId: env_variables_js_1.WORKOS_CLIENT_ID,
|
|
42
|
+
refreshToken: session.refreshToken,
|
|
43
|
+
});
|
|
44
|
+
if (debug)
|
|
45
|
+
console.log('Refresh successful:', refreshToken);
|
|
46
|
+
// Encrypt session with new access and refresh tokens
|
|
47
|
+
const encryptedSession = await encryptSession({
|
|
48
|
+
accessToken,
|
|
49
|
+
refreshToken,
|
|
50
|
+
user: session.user,
|
|
51
|
+
impersonator: session.impersonator,
|
|
52
|
+
});
|
|
53
|
+
newRequestHeaders.set(sessionHeaderName, encryptedSession);
|
|
54
|
+
const response = server_1.NextResponse.next({
|
|
55
|
+
request: {
|
|
56
|
+
headers: newRequestHeaders,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
// update the cookie
|
|
60
|
+
response.cookies.set(cookie_js_1.cookieName, encryptedSession, cookie_js_1.cookieOptions);
|
|
61
|
+
return response;
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
console.warn('Failed to refresh', e);
|
|
65
|
+
const response = server_1.NextResponse.next();
|
|
66
|
+
response.cookies.delete(cookie_js_1.cookieName);
|
|
67
|
+
return response;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.updateSession = updateSession;
|
|
71
|
+
async function getUser({ ensureSignedIn = false } = {}) {
|
|
72
|
+
var _a;
|
|
73
|
+
const session = await getSessionFromHeader();
|
|
74
|
+
if (!session) {
|
|
75
|
+
if (ensureSignedIn) {
|
|
76
|
+
const returnPathname = (_a = (0, headers_1.headers)().get('next-url')) !== null && _a !== void 0 ? _a : undefined;
|
|
77
|
+
(0, navigation_1.redirect)(await (0, get_authorization_url_js_1.getAuthorizationUrl)(returnPathname));
|
|
78
|
+
}
|
|
79
|
+
return { user: null };
|
|
80
|
+
}
|
|
81
|
+
const { sid: sessionId, org_id: organizationId, role } = (0, jose_1.decodeJwt)(session.accessToken);
|
|
82
|
+
return {
|
|
83
|
+
sessionId,
|
|
84
|
+
user: session.user,
|
|
85
|
+
organizationId,
|
|
86
|
+
role,
|
|
87
|
+
impersonator: session.impersonator,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
exports.getUser = getUser;
|
|
91
|
+
async function terminateSession() {
|
|
92
|
+
const { sessionId } = await getUser();
|
|
93
|
+
if (sessionId) {
|
|
94
|
+
(0, navigation_1.redirect)(workos_js_1.workos.userManagement.getLogoutUrl({ sessionId }));
|
|
95
|
+
}
|
|
96
|
+
(0, navigation_1.redirect)('/');
|
|
97
|
+
}
|
|
98
|
+
exports.terminateSession = terminateSession;
|
|
99
|
+
async function verifyAccessToken(accessToken) {
|
|
100
|
+
try {
|
|
101
|
+
await (0, jose_1.jwtVerify)(accessToken, JWKS);
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
console.warn('Failed to verify session:', e);
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function getSessionFromCookie() {
|
|
110
|
+
const cookie = (0, headers_1.cookies)().get(cookie_js_1.cookieName);
|
|
111
|
+
if (cookie) {
|
|
112
|
+
return (0, iron_session_1.unsealData)(cookie.value, {
|
|
113
|
+
password: env_variables_js_1.WORKOS_COOKIE_PASSWORD,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function getSessionFromHeader() {
|
|
118
|
+
const authHeader = (0, headers_1.headers)().get(sessionHeaderName);
|
|
119
|
+
if (!authHeader)
|
|
120
|
+
return;
|
|
121
|
+
return (0, iron_session_1.unsealData)(authHeader, { password: env_variables_js_1.WORKOS_COOKIE_PASSWORD });
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.workos = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const node_1 = tslib_1.__importDefault(require("@workos-inc/node"));
|
|
6
|
+
const env_variables_js_1 = require("./env-variables.js");
|
|
7
|
+
// Initialize the WorkOS client
|
|
8
|
+
const workos = new node_1.default(env_variables_js_1.WORKOS_API_KEY);
|
|
9
|
+
exports.workos = workos;
|
|
10
|
+
//# sourceMappingURL=workos.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workos.js","sourceRoot":"","sources":["../../src/workos.ts"],"names":[],"mappings":";;;;AAAA,oEAAsC;AACtC,yDAAoD;AAEpD,+BAA+B;AAC/B,MAAM,MAAM,GAAG,IAAI,cAAM,CAAC,iCAAc,CAAC,CAAC;AAEjC,wBAAM"}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@workos-inc/authkit-nextjs",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Authentication and session helpers for using WorkOS & AuthKit with Next.js",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"main": "./dist/cjs/index.js",
|
|
8
|
+
"types": "./dist/cjs/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"src",
|
|
12
|
+
"LICENSE",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"clean": "rm -rf dist",
|
|
17
|
+
"prebuild": "npm run clean",
|
|
18
|
+
"build": "tsc --project tsconfig-cjs.json",
|
|
19
|
+
"preversion": "npm run build",
|
|
20
|
+
"postversion": "git push --follow-tags",
|
|
21
|
+
"prepublishOnly": "npm run lint",
|
|
22
|
+
"lint": "eslint \"src/**/*.ts*\"",
|
|
23
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@workos-inc/node": "^6.7.0",
|
|
27
|
+
"iron-session": "^8.0.1",
|
|
28
|
+
"jose": "^5.2.3"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"next": "^13.5.4 || ^14.0.3",
|
|
32
|
+
"react": "^18.0",
|
|
33
|
+
"react-dom": "^18.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^20.11.28",
|
|
37
|
+
"@types/react": "18.2.67",
|
|
38
|
+
"@types/react-dom": "18.2.22",
|
|
39
|
+
"eslint": "^8.29.0",
|
|
40
|
+
"eslint-config-prettier": "^9.1.0",
|
|
41
|
+
"eslint-plugin-require-extensions": "^0.1.3",
|
|
42
|
+
"next": "^14.1.3",
|
|
43
|
+
"typescript": "5.4.2",
|
|
44
|
+
"typescript-eslint": "^7.2.0"
|
|
45
|
+
},
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"homepage": "https://github.com/workos/authkit-nextjs#readme",
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/workos/authkit-nextjs.git"
|
|
51
|
+
},
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/workos/authkit-nextjs/issues"
|
|
54
|
+
}
|
|
55
|
+
}
|
package/src/auth.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getAuthorizationUrl } from './get-authorization-url.js';
|
|
2
|
+
import { cookies } from 'next/headers';
|
|
3
|
+
import { cookieName } from './cookie.js';
|
|
4
|
+
import { terminateSession } from './session.js';
|
|
5
|
+
|
|
6
|
+
async function getSignInUrl() {
|
|
7
|
+
return getAuthorizationUrl();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function signOut() {
|
|
11
|
+
cookies().delete(cookieName);
|
|
12
|
+
await terminateSession();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { getSignInUrl, signOut };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { cookies } from 'next/headers';
|
|
3
|
+
import { workos } from './workos.js';
|
|
4
|
+
import { WORKOS_CLIENT_ID } from './env-variables.js';
|
|
5
|
+
import { encryptSession } from './session.js';
|
|
6
|
+
import { cookieName, cookieOptions } from './cookie.js';
|
|
7
|
+
import { HandleAuthOptions } from './interfaces.js';
|
|
8
|
+
|
|
9
|
+
export function handleAuth(options: HandleAuthOptions = {}) {
|
|
10
|
+
const { returnPathname: returnPathnameOption = '/' } = options;
|
|
11
|
+
|
|
12
|
+
return async function GET(request: NextRequest) {
|
|
13
|
+
const code = request.nextUrl.searchParams.get('code');
|
|
14
|
+
const state = request.nextUrl.searchParams.get('state');
|
|
15
|
+
const returnPathname = state ? JSON.parse(atob(state)).returnPathname : null;
|
|
16
|
+
|
|
17
|
+
if (code) {
|
|
18
|
+
try {
|
|
19
|
+
// Use the code returned to us by AuthKit and authenticate the user with WorkOS
|
|
20
|
+
const { accessToken, refreshToken, user, impersonator } = await workos.userManagement.authenticateWithCode({
|
|
21
|
+
clientId: WORKOS_CLIENT_ID,
|
|
22
|
+
code,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const url = request.nextUrl.clone();
|
|
26
|
+
|
|
27
|
+
// Cleanup params
|
|
28
|
+
url.searchParams.delete('code');
|
|
29
|
+
url.searchParams.delete('state');
|
|
30
|
+
|
|
31
|
+
// Redirect to the requested path and store the session
|
|
32
|
+
url.pathname = returnPathname ?? returnPathnameOption;
|
|
33
|
+
|
|
34
|
+
const response = NextResponse.redirect(url);
|
|
35
|
+
|
|
36
|
+
if (!accessToken || !refreshToken) throw new Error('response is missing tokens');
|
|
37
|
+
|
|
38
|
+
// The refreshToken should never be accesible publicly, hence why we encrypt it in the cookie session
|
|
39
|
+
// Alternatively you could persist the refresh token in a backend database
|
|
40
|
+
const session = await encryptSession({ accessToken, refreshToken, user, impersonator });
|
|
41
|
+
cookies().set(cookieName, session, cookieOptions);
|
|
42
|
+
|
|
43
|
+
return response;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
const errorRes = {
|
|
46
|
+
error: error instanceof Error ? error.message : String(error),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
console.error(errorRes);
|
|
50
|
+
|
|
51
|
+
return errorResponse();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return errorResponse();
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function errorResponse() {
|
|
59
|
+
return NextResponse.json(
|
|
60
|
+
{
|
|
61
|
+
error: {
|
|
62
|
+
message: 'Something went wrong',
|
|
63
|
+
description: 'Couldn’t sign in. If you are not sure what happened, please contact your organization admin.',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{ status: 500 },
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/button.tsx
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
const Button = React.forwardRef<HTMLButtonElement, React.ComponentPropsWithoutRef<'button'>>((props, forwardedRef) => {
|
|
4
|
+
return (
|
|
5
|
+
<button
|
|
6
|
+
ref={forwardedRef}
|
|
7
|
+
type="button"
|
|
8
|
+
{...props}
|
|
9
|
+
style={{
|
|
10
|
+
display: 'inline-flex',
|
|
11
|
+
alignItems: 'center',
|
|
12
|
+
justifyContent: 'center',
|
|
13
|
+
flexShrink: 0,
|
|
14
|
+
height: '1.714em',
|
|
15
|
+
padding: '0 0.6em',
|
|
16
|
+
|
|
17
|
+
fontFamily: 'inherit',
|
|
18
|
+
fontSize: 'inherit',
|
|
19
|
+
borderRadius: 'min(max(calc(var(--wi-s) * 0.6), 1px), 7px)',
|
|
20
|
+
border: 'none',
|
|
21
|
+
backgroundColor: 'var(--wi-c)',
|
|
22
|
+
color: 'white',
|
|
23
|
+
|
|
24
|
+
...props.style,
|
|
25
|
+
}}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
Button.displayName = 'Button';
|
|
31
|
+
|
|
32
|
+
export { Button };
|
package/src/cookie.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
function getEnvVariable(name: string) {
|
|
2
|
+
const envVariable = process.env[name];
|
|
3
|
+
if (!envVariable) {
|
|
4
|
+
throw new Error(`${name} environment variable is not set`);
|
|
5
|
+
}
|
|
6
|
+
return envVariable;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const WORKOS_CLIENT_ID = getEnvVariable('WORKOS_CLIENT_ID');
|
|
10
|
+
const WORKOS_API_KEY = getEnvVariable('WORKOS_API_KEY');
|
|
11
|
+
const WORKOS_REDIRECT_URI = getEnvVariable('WORKOS_REDIRECT_URI');
|
|
12
|
+
const WORKOS_COOKIE_PASSWORD = getEnvVariable('WORKOS_COOKIE_PASSWORD');
|
|
13
|
+
|
|
14
|
+
if (WORKOS_COOKIE_PASSWORD.length < 32) {
|
|
15
|
+
throw new Error('WORKOS_COOKIE_PASSWORD must be at least 32 characters long');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { WORKOS_CLIENT_ID, WORKOS_API_KEY, WORKOS_REDIRECT_URI, WORKOS_COOKIE_PASSWORD };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { workos } from './workos.js';
|
|
2
|
+
import { WORKOS_CLIENT_ID, WORKOS_REDIRECT_URI } from './env-variables.js';
|
|
3
|
+
|
|
4
|
+
async function getAuthorizationUrl(returnPathname?: string) {
|
|
5
|
+
return workos.userManagement.getAuthorizationUrl({
|
|
6
|
+
provider: 'authkit',
|
|
7
|
+
clientId: WORKOS_CLIENT_ID,
|
|
8
|
+
redirectUri: WORKOS_REDIRECT_URI,
|
|
9
|
+
state: returnPathname ? btoa(JSON.stringify({ returnPathname })) : undefined,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { getAuthorizationUrl };
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { getUser } from './session.js';
|
|
3
|
+
import { signOut } from './auth.js';
|
|
4
|
+
import { workos } from './workos.js';
|
|
5
|
+
import { Button } from './button.js';
|
|
6
|
+
import { MinMaxButton } from './min-max-button.js';
|
|
7
|
+
|
|
8
|
+
interface ImpersonationProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
9
|
+
side?: 'top' | 'bottom';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function Impersonation({ side = 'bottom', ...props }: ImpersonationProps) {
|
|
13
|
+
const { impersonator, user, organizationId } = await getUser();
|
|
14
|
+
|
|
15
|
+
if (!impersonator) return null;
|
|
16
|
+
|
|
17
|
+
const organization = organizationId ? await workos.organizations.getOrganization(organizationId) : null;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
{...props}
|
|
22
|
+
data-workos-impersonation-root=""
|
|
23
|
+
style={{
|
|
24
|
+
'position': 'fixed',
|
|
25
|
+
'inset': 0,
|
|
26
|
+
'pointerEvents': 'none',
|
|
27
|
+
'zIndex': 9999,
|
|
28
|
+
|
|
29
|
+
// short properties with defaults for authoring convenience
|
|
30
|
+
'--wi-minimized': '0',
|
|
31
|
+
'--wi-s': 'min(max(var(--workos-impersonation-size, 4px), 2px), 15px)',
|
|
32
|
+
'--wi-bgc': 'var(--workos-impersonation-background-color, #fce654)',
|
|
33
|
+
'--wi-c': 'var(--workos-impersonation-color, #1a1600)',
|
|
34
|
+
'--wi-bc': 'var(--workos-impersonation-border-color, #e0c36c)',
|
|
35
|
+
'--wi-bw': 'var(--workos-impersonation-border-width, 1px)',
|
|
36
|
+
|
|
37
|
+
...props.style,
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
<div
|
|
41
|
+
style={{
|
|
42
|
+
'--wi-frame-size': 'calc(var(--wi-s) * (1 - var(--wi-minimized)) + var(--wi-minimized) * var(--wi-bw) * -1)',
|
|
43
|
+
'position': 'absolute',
|
|
44
|
+
'inset': 'calc(var(--wi-frame-size) * -1)',
|
|
45
|
+
'borderRadius': 'calc(var(--wi-frame-size) * 3)',
|
|
46
|
+
'boxShadow': `
|
|
47
|
+
inset 0 0 0 calc(var(--wi-frame-size) * 2) var(--wi-bgc),
|
|
48
|
+
inset 0 0 0 calc(var(--wi-frame-size) * 2 + var(--wi-bw)) var(--wi-bc)
|
|
49
|
+
`,
|
|
50
|
+
'transition': 'all 500ms cubic-bezier(0.16, 1, 0.3, 1)',
|
|
51
|
+
}}
|
|
52
|
+
/>
|
|
53
|
+
|
|
54
|
+
<div
|
|
55
|
+
style={{
|
|
56
|
+
display: 'flex',
|
|
57
|
+
justifyContent: 'center',
|
|
58
|
+
|
|
59
|
+
position: 'fixed',
|
|
60
|
+
left: 0,
|
|
61
|
+
right: 0,
|
|
62
|
+
...(side === 'top' && { top: 'var(--wi-s)' }),
|
|
63
|
+
...(side === 'bottom' && { bottom: 'var(--wi-s)' }),
|
|
64
|
+
|
|
65
|
+
fontFamily:
|
|
66
|
+
"system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif",
|
|
67
|
+
fontSize: 'calc(12px + var(--wi-s) * 0.5)',
|
|
68
|
+
lineHeight: '1.4',
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<form
|
|
72
|
+
action={async () => {
|
|
73
|
+
'use server';
|
|
74
|
+
await signOut();
|
|
75
|
+
}}
|
|
76
|
+
style={{
|
|
77
|
+
display: 'flex',
|
|
78
|
+
alignItems: 'baseline',
|
|
79
|
+
paddingLeft: 'var(--wi-s)',
|
|
80
|
+
paddingRight: 'var(--wi-s)',
|
|
81
|
+
|
|
82
|
+
position: 'relative',
|
|
83
|
+
marginLeft: 'calc(var(--wi-s) * 2)',
|
|
84
|
+
marginRight: 'calc(var(--wi-s) * 2)',
|
|
85
|
+
|
|
86
|
+
pointerEvents: 'auto',
|
|
87
|
+
backgroundColor: 'var(--wi-bgc)',
|
|
88
|
+
borderStyle: 'solid',
|
|
89
|
+
borderColor: 'var(--wi-bc)',
|
|
90
|
+
borderLeftWidth: 'var(--wi-bw)',
|
|
91
|
+
borderRightWidth: 'var(--wi-bw)',
|
|
92
|
+
|
|
93
|
+
transition: 'all 500ms cubic-bezier(0.16, 1, 0.3, 1)',
|
|
94
|
+
transform: `translateX(calc(var(--wi-minimized) * (var(--wi-s) * 10 - 5%)))`,
|
|
95
|
+
opacity: 'calc(1 - var(--wi-minimized))',
|
|
96
|
+
zIndex: 'calc(1 - var(--wi-minimized))',
|
|
97
|
+
|
|
98
|
+
...(side === 'top' && {
|
|
99
|
+
paddingTop: 0,
|
|
100
|
+
paddingBottom: 'var(--wi-s)',
|
|
101
|
+
borderTopWidth: 0,
|
|
102
|
+
borderBottomWidth: 'var(--wi-bw)',
|
|
103
|
+
borderBottomLeftRadius: 'var(--wi-s)',
|
|
104
|
+
borderBottomRightRadius: 'var(--wi-s)',
|
|
105
|
+
}),
|
|
106
|
+
|
|
107
|
+
...(side === 'bottom' && {
|
|
108
|
+
paddingTop: 'var(--wi-s)',
|
|
109
|
+
paddingBottom: 0,
|
|
110
|
+
borderTopWidth: 'var(--wi-bw)',
|
|
111
|
+
borderBottomWidth: 0,
|
|
112
|
+
borderTopLeftRadius: 'var(--wi-s)',
|
|
113
|
+
borderTopRightRadius: 'var(--wi-s)',
|
|
114
|
+
}),
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
<p style={{ all: 'unset', color: 'var(--wi-c)', textWrap: 'balance', marginLeft: 'var(--wi-s)' }}>
|
|
118
|
+
You are impersonating <b>{user.email}</b>{' '}
|
|
119
|
+
{organization !== null && (
|
|
120
|
+
<>
|
|
121
|
+
within the <b>{organization.name}</b> organization
|
|
122
|
+
</>
|
|
123
|
+
)}
|
|
124
|
+
</p>
|
|
125
|
+
<Button type="submit" style={{ marginLeft: 'calc(var(--wi-s) * 2)', marginRight: 'var(--wi-s)' }}>
|
|
126
|
+
Stop
|
|
127
|
+
</Button>
|
|
128
|
+
<MinMaxButton minimizedValue="1">{side === 'top' ? '↗' : '↘'}</MinMaxButton>
|
|
129
|
+
</form>
|
|
130
|
+
|
|
131
|
+
<div
|
|
132
|
+
style={{
|
|
133
|
+
padding: 'var(--wi-s)',
|
|
134
|
+
|
|
135
|
+
position: 'fixed',
|
|
136
|
+
right: 'var(--wi-s)',
|
|
137
|
+
|
|
138
|
+
pointerEvents: 'auto',
|
|
139
|
+
backgroundColor: 'var(--wi-bgc)',
|
|
140
|
+
border: 'var(--wi-bw) solid var(--wi-bc)',
|
|
141
|
+
borderRadius: 'var(--wi-s)',
|
|
142
|
+
|
|
143
|
+
transition: 'all 500ms cubic-bezier(0.16, 1, 0.3, 1)',
|
|
144
|
+
transform: 'translateX(calc((1 - var(--wi-minimized)) * var(--wi-s) * -5))',
|
|
145
|
+
opacity: 'var(--wi-minimized)',
|
|
146
|
+
zIndex: 'var(--wi-minimized)',
|
|
147
|
+
|
|
148
|
+
...(side === 'top' && { top: 'var(--wi-s)' }),
|
|
149
|
+
...(side === 'bottom' && { bottom: 'var(--wi-s)' }),
|
|
150
|
+
}}
|
|
151
|
+
>
|
|
152
|
+
<MinMaxButton minimizedValue="0">{side === 'top' ? '↙' : '↖'}</MinMaxButton>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { handleAuth } from './authkit-callback-route.js';
|
|
2
|
+
import { authkitMiddleware } from './middleware.js';
|
|
3
|
+
import { getUser } from './session.js';
|
|
4
|
+
import { getSignInUrl, signOut } from './auth.js';
|
|
5
|
+
import { Impersonation } from './impersonation.js';
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
handleAuth,
|
|
9
|
+
//
|
|
10
|
+
authkitMiddleware,
|
|
11
|
+
//
|
|
12
|
+
getSignInUrl,
|
|
13
|
+
getUser,
|
|
14
|
+
signOut,
|
|
15
|
+
//
|
|
16
|
+
Impersonation,
|
|
17
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { User } from '@workos-inc/node';
|
|
2
|
+
|
|
3
|
+
export interface HandleAuthOptions {
|
|
4
|
+
returnPathname?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface Impersonator {
|
|
8
|
+
email: string;
|
|
9
|
+
reason: string | null;
|
|
10
|
+
}
|
|
11
|
+
export interface Session {
|
|
12
|
+
accessToken: string;
|
|
13
|
+
refreshToken: string;
|
|
14
|
+
user: User;
|
|
15
|
+
impersonator?: Impersonator;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface UserInfo {
|
|
19
|
+
user: User;
|
|
20
|
+
sessionId: string;
|
|
21
|
+
organizationId?: string;
|
|
22
|
+
role?: string;
|
|
23
|
+
impersonator?: Impersonator;
|
|
24
|
+
}
|
|
25
|
+
export interface NoUserInfo {
|
|
26
|
+
user: null;
|
|
27
|
+
sessionId?: undefined;
|
|
28
|
+
organizationId?: undefined;
|
|
29
|
+
role?: undefined;
|
|
30
|
+
impersonator?: undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface AccessToken {
|
|
34
|
+
sid: string;
|
|
35
|
+
org_id?: string;
|
|
36
|
+
role?: string;
|
|
37
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { NextMiddleware } from 'next/server';
|
|
2
|
+
import { updateSession } from './session.js';
|
|
3
|
+
|
|
4
|
+
interface AuthkitMiddlewareOptions {
|
|
5
|
+
debug?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function authkitMiddleware({ debug = false }: AuthkitMiddlewareOptions = {}): NextMiddleware {
|
|
9
|
+
return function (request) {
|
|
10
|
+
return updateSession(request, debug);
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Button } from './button.js';
|
|
5
|
+
|
|
6
|
+
interface MinMaxButtonProps {
|
|
7
|
+
children?: React.ReactNode;
|
|
8
|
+
minimizedValue: '0' | '1';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function MinMaxButton({ children, minimizedValue }: MinMaxButtonProps) {
|
|
12
|
+
return (
|
|
13
|
+
<Button
|
|
14
|
+
onClick={() => {
|
|
15
|
+
const root = document.querySelector('[data-workos-impersonation-root]') as HTMLElement | null;
|
|
16
|
+
root?.style.setProperty('--wi-minimized', minimizedValue);
|
|
17
|
+
}}
|
|
18
|
+
style={{ padding: 0, width: '1.714em' }}
|
|
19
|
+
>
|
|
20
|
+
{children}
|
|
21
|
+
</Button>
|
|
22
|
+
);
|
|
23
|
+
}
|