@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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +145 -0
  3. package/dist/cjs/auth.d.ts +3 -0
  4. package/dist/cjs/auth.js +17 -0
  5. package/dist/cjs/auth.js.map +1 -0
  6. package/dist/cjs/authkit-callback-route.d.ts +3 -0
  7. package/dist/cjs/authkit-callback-route.js +58 -0
  8. package/dist/cjs/authkit-callback-route.js.map +1 -0
  9. package/dist/cjs/button.d.ts +3 -0
  10. package/dist/cjs/button.js +25 -0
  11. package/dist/cjs/button.js.map +1 -0
  12. package/dist/cjs/cookie.d.ts +8 -0
  13. package/dist/cjs/cookie.js +13 -0
  14. package/dist/cjs/cookie.js.map +1 -0
  15. package/dist/cjs/env-variables.d.ts +5 -0
  16. package/dist/cjs/env-variables.js +22 -0
  17. package/dist/cjs/env-variables.js.map +1 -0
  18. package/dist/cjs/get-authorization-url.d.ts +2 -0
  19. package/dist/cjs/get-authorization-url.js +15 -0
  20. package/dist/cjs/get-authorization-url.js.map +1 -0
  21. package/dist/cjs/impersonation.d.ts +6 -0
  22. package/dist/cjs/impersonation.js +119 -0
  23. package/dist/cjs/impersonation.js.map +1 -0
  24. package/dist/cjs/index.d.ts +6 -0
  25. package/dist/cjs/index.js +15 -0
  26. package/dist/cjs/index.js.map +1 -0
  27. package/dist/cjs/interfaces.d.ts +33 -0
  28. package/dist/cjs/interfaces.js +3 -0
  29. package/dist/cjs/interfaces.js.map +1 -0
  30. package/dist/cjs/middleware.d.ts +6 -0
  31. package/dist/cjs/middleware.js +11 -0
  32. package/dist/cjs/middleware.js.map +1 -0
  33. package/dist/cjs/min-max-button.d.ts +7 -0
  34. package/dist/cjs/min-max-button.js +15 -0
  35. package/dist/cjs/min-max-button.js.map +1 -0
  36. package/dist/cjs/session.d.ts +12 -0
  37. package/dist/cjs/session.js +123 -0
  38. package/dist/cjs/session.js.map +1 -0
  39. package/dist/cjs/workos.d.ts +3 -0
  40. package/dist/cjs/workos.js +10 -0
  41. package/dist/cjs/workos.js.map +1 -0
  42. package/package.json +55 -0
  43. package/src/auth.ts +15 -0
  44. package/src/authkit-callback-route.ts +69 -0
  45. package/src/button.tsx +32 -0
  46. package/src/cookie.ts +9 -0
  47. package/src/env-variables.ts +18 -0
  48. package/src/get-authorization-url.ts +13 -0
  49. package/src/impersonation.tsx +157 -0
  50. package/src/index.ts +17 -0
  51. package/src/interfaces.ts +37 -0
  52. package/src/middleware.ts +12 -0
  53. package/src/min-max-button.tsx +23 -0
  54. package/src/session.ts +137 -0
  55. 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,3 @@
1
+ import WorkOS from '@workos-inc/node';
2
+ declare const workos: WorkOS;
3
+ export { workos };
@@ -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,9 @@
1
+ const cookieName = 'wos-session';
2
+ const cookieOptions = {
3
+ path: '/',
4
+ httpOnly: true,
5
+ secure: true,
6
+ sameSite: 'lax' as const,
7
+ };
8
+
9
+ export { cookieName, cookieOptions };
@@ -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
+ }