@workos-inc/authkit-nextjs 0.17.2 → 1.0.1
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 +102 -25
- package/dist/esm/actions.js +11 -0
- package/dist/esm/actions.js.map +1 -1
- package/dist/esm/components/authkit-provider.js +128 -0
- package/dist/esm/components/authkit-provider.js.map +1 -0
- package/dist/esm/components/button.js.map +1 -0
- package/dist/esm/{impersonation.js → components/impersonation.js} +12 -7
- package/dist/esm/components/impersonation.js.map +1 -0
- package/dist/esm/components/index.js +4 -0
- package/dist/esm/components/index.js.map +1 -0
- package/dist/esm/components/min-max-button.js.map +1 -0
- package/dist/esm/cookie.js +15 -12
- package/dist/esm/cookie.js.map +1 -1
- package/dist/esm/env-variables.js +5 -2
- package/dist/esm/env-variables.js.map +1 -1
- package/dist/esm/index.js +4 -8
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/middleware.js +5 -2
- package/dist/esm/middleware.js.map +1 -1
- package/dist/esm/session.js +117 -93
- package/dist/esm/session.js.map +1 -1
- package/dist/esm/types/actions.d.ts +6 -0
- package/dist/esm/types/components/authkit-provider.d.ts +33 -0
- package/dist/esm/types/{impersonation.d.ts → components/impersonation.d.ts} +1 -1
- package/dist/esm/types/components/index.d.ts +3 -0
- package/dist/esm/types/cookie.d.ts +4 -0
- package/dist/esm/types/env-variables.d.ts +2 -2
- package/dist/esm/types/index.d.ts +3 -5
- package/dist/esm/types/interfaces.d.ts +11 -0
- package/dist/esm/types/middleware.d.ts +3 -2
- package/dist/esm/types/session.d.ts +5 -28
- package/dist/esm/types/utils.d.ts +1 -1
- package/dist/esm/types/workos.d.ts +1 -1
- package/dist/esm/utils.js +5 -3
- package/dist/esm/utils.js.map +1 -1
- package/dist/esm/workos.js +1 -1
- package/dist/esm/workos.js.map +1 -1
- package/package.json +11 -1
- package/src/actions.ts +20 -0
- package/src/components/authkit-provider.tsx +169 -0
- package/src/{impersonation.tsx → components/impersonation.tsx} +14 -7
- package/src/components/index.ts +4 -0
- package/src/cookie.ts +33 -12
- package/src/env-variables.ts +7 -2
- package/src/index.ts +3 -8
- package/src/interfaces.ts +13 -0
- package/src/middleware.ts +8 -4
- package/src/session.ts +159 -110
- package/src/utils.ts +6 -3
- package/src/workos.ts +1 -1
- package/dist/esm/authkit-provider.js +0 -52
- package/dist/esm/authkit-provider.js.map +0 -1
- package/dist/esm/button.js.map +0 -1
- package/dist/esm/impersonation.js.map +0 -1
- package/dist/esm/min-max-button.js.map +0 -1
- package/dist/esm/types/authkit-provider.d.ts +0 -11
- package/src/authkit-provider.tsx +0 -66
- /package/dist/esm/{button.js → components/button.js} +0 -0
- /package/dist/esm/{min-max-button.js → components/min-max-button.js} +0 -0
- /package/dist/esm/types/{button.d.ts → components/button.d.ts} +0 -0
- /package/dist/esm/types/{min-max-button.d.ts → components/min-max-button.d.ts} +0 -0
- /package/src/{button.tsx → components/button.tsx} +0 -0
- /package/src/{min-max-button.tsx → components/min-max-button.tsx} +0 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { createContext, ReactNode, useContext, useEffect, useState } from 'react';
|
|
4
|
+
import { checkSessionAction, getAuthAction, refreshAuthAction } from '../actions.js';
|
|
5
|
+
import type { Impersonator, User } from '@workos-inc/node';
|
|
6
|
+
|
|
7
|
+
type AuthContextType = {
|
|
8
|
+
user: User | null;
|
|
9
|
+
sessionId: string | undefined;
|
|
10
|
+
organizationId: string | undefined;
|
|
11
|
+
role: string | undefined;
|
|
12
|
+
permissions: string[] | undefined;
|
|
13
|
+
entitlements: string[] | undefined;
|
|
14
|
+
impersonator: Impersonator | undefined;
|
|
15
|
+
accessToken: string | undefined;
|
|
16
|
+
loading: boolean;
|
|
17
|
+
getAuth: (options?: { ensureSignedIn?: boolean }) => Promise<void>;
|
|
18
|
+
refreshAuth: (options?: { ensureSignedIn?: boolean; organizationId?: string }) => Promise<void | { error: string }>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
|
22
|
+
|
|
23
|
+
interface AuthKitProviderProps {
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
/**
|
|
26
|
+
* Customize what happens when a session is expired. By default,the entire page will be reloaded.
|
|
27
|
+
* You can also pass this as `false` to disable the expired session checks.
|
|
28
|
+
*/
|
|
29
|
+
onSessionExpired?: false | (() => void);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const AuthKitProvider = ({ children, onSessionExpired }: AuthKitProviderProps) => {
|
|
33
|
+
const [user, setUser] = useState<User | null>(null);
|
|
34
|
+
const [sessionId, setSessionId] = useState<string | undefined>(undefined);
|
|
35
|
+
const [organizationId, setOrganizationId] = useState<string | undefined>(undefined);
|
|
36
|
+
const [role, setRole] = useState<string | undefined>(undefined);
|
|
37
|
+
const [permissions, setPermissions] = useState<string[] | undefined>(undefined);
|
|
38
|
+
const [entitlements, setEntitlements] = useState<string[] | undefined>(undefined);
|
|
39
|
+
const [impersonator, setImpersonator] = useState<Impersonator | undefined>(undefined);
|
|
40
|
+
const [accessToken, setAccessToken] = useState<string | undefined>(undefined);
|
|
41
|
+
const [loading, setLoading] = useState(true);
|
|
42
|
+
|
|
43
|
+
const getAuth = async ({ ensureSignedIn = false }: { ensureSignedIn?: boolean } = {}) => {
|
|
44
|
+
try {
|
|
45
|
+
const auth = await getAuthAction(ensureSignedIn);
|
|
46
|
+
setUser(auth.user);
|
|
47
|
+
setSessionId(auth.sessionId);
|
|
48
|
+
setOrganizationId(auth.organizationId);
|
|
49
|
+
setRole(auth.role);
|
|
50
|
+
setPermissions(auth.permissions);
|
|
51
|
+
setEntitlements(auth.entitlements);
|
|
52
|
+
setImpersonator(auth.impersonator);
|
|
53
|
+
setAccessToken(auth.accessToken);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
setUser(null);
|
|
56
|
+
setSessionId(undefined);
|
|
57
|
+
setOrganizationId(undefined);
|
|
58
|
+
setRole(undefined);
|
|
59
|
+
setPermissions(undefined);
|
|
60
|
+
setEntitlements(undefined);
|
|
61
|
+
setImpersonator(undefined);
|
|
62
|
+
setAccessToken(undefined);
|
|
63
|
+
} finally {
|
|
64
|
+
setLoading(false);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const refreshAuth = async ({
|
|
69
|
+
ensureSignedIn = false,
|
|
70
|
+
organizationId,
|
|
71
|
+
}: { ensureSignedIn?: boolean; organizationId?: string } = {}) => {
|
|
72
|
+
try {
|
|
73
|
+
setLoading(true);
|
|
74
|
+
const auth = await refreshAuthAction({ ensureSignedIn, organizationId });
|
|
75
|
+
|
|
76
|
+
setUser(auth.user);
|
|
77
|
+
setSessionId(auth.sessionId);
|
|
78
|
+
setOrganizationId(auth.organizationId);
|
|
79
|
+
setRole(auth.role);
|
|
80
|
+
setPermissions(auth.permissions);
|
|
81
|
+
setEntitlements(auth.entitlements);
|
|
82
|
+
setImpersonator(auth.impersonator);
|
|
83
|
+
setAccessToken(auth.accessToken);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return error instanceof Error ? { error: error.message } : { error: String(error) };
|
|
86
|
+
} finally {
|
|
87
|
+
setLoading(false);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
getAuth();
|
|
93
|
+
|
|
94
|
+
// Return early if the session expired checks are disabled.
|
|
95
|
+
if (onSessionExpired === false) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let visibilityChangedCalled = false;
|
|
100
|
+
|
|
101
|
+
const handleVisibilityChange = async () => {
|
|
102
|
+
if (visibilityChangedCalled) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// In the case where we're using middleware auth mode, a user that has signed out in a different tab
|
|
107
|
+
// will run into an issue if they attempt to hit a server action in the original tab.
|
|
108
|
+
// This will force a refresh of the page in that case, which will redirect them to the sign-in page.
|
|
109
|
+
if (document.visibilityState === 'visible') {
|
|
110
|
+
visibilityChangedCalled = true;
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const hasSession = await checkSessionAction();
|
|
114
|
+
if (!hasSession) {
|
|
115
|
+
throw new Error('Session expired');
|
|
116
|
+
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
// 'Failed to fetch' is the error we are looking for if the action fails
|
|
119
|
+
// If any other error happens, for other reasons, we should not reload the page
|
|
120
|
+
if (error instanceof Error && error.message.includes('Failed to fetch')) {
|
|
121
|
+
if (onSessionExpired) {
|
|
122
|
+
onSessionExpired();
|
|
123
|
+
} else {
|
|
124
|
+
window.location.reload();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} finally {
|
|
128
|
+
visibilityChangedCalled = false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
window.addEventListener('visibilitychange', handleVisibilityChange);
|
|
134
|
+
window.addEventListener('focus', handleVisibilityChange);
|
|
135
|
+
|
|
136
|
+
return () => {
|
|
137
|
+
window.removeEventListener('focus', handleVisibilityChange);
|
|
138
|
+
window.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
139
|
+
};
|
|
140
|
+
}, [onSessionExpired]);
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<AuthContext.Provider
|
|
144
|
+
value={{
|
|
145
|
+
user,
|
|
146
|
+
sessionId,
|
|
147
|
+
organizationId,
|
|
148
|
+
role,
|
|
149
|
+
permissions,
|
|
150
|
+
entitlements,
|
|
151
|
+
impersonator,
|
|
152
|
+
accessToken,
|
|
153
|
+
loading,
|
|
154
|
+
getAuth,
|
|
155
|
+
refreshAuth,
|
|
156
|
+
}}
|
|
157
|
+
>
|
|
158
|
+
{children}
|
|
159
|
+
</AuthContext.Provider>
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export function useAuth() {
|
|
164
|
+
const context = useContext(AuthContext);
|
|
165
|
+
if (!context) {
|
|
166
|
+
throw new Error('useAuth must be used within an AuthKitProvider');
|
|
167
|
+
}
|
|
168
|
+
return context;
|
|
169
|
+
}
|
|
@@ -1,20 +1,27 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
1
3
|
import * as React from 'react';
|
|
2
|
-
import { withAuth } from './session.js';
|
|
3
|
-
import { workos } from './workos.js';
|
|
4
4
|
import { Button } from './button.js';
|
|
5
5
|
import { MinMaxButton } from './min-max-button.js';
|
|
6
|
-
import { handleSignOutAction } from '
|
|
6
|
+
import { getOrganizationAction, handleSignOutAction } from '../actions.js';
|
|
7
|
+
import type { Organization } from '@workos-inc/node';
|
|
8
|
+
import { useAuth } from './authkit-provider.js';
|
|
7
9
|
|
|
8
10
|
interface ImpersonationProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
9
11
|
side?: 'top' | 'bottom';
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
export
|
|
13
|
-
const {
|
|
14
|
+
export function Impersonation({ side = 'bottom', ...props }: ImpersonationProps) {
|
|
15
|
+
const { user, impersonator, organizationId, loading } = useAuth();
|
|
16
|
+
|
|
17
|
+
const [organization, setOrganization] = React.useState<Organization | null>(null);
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
React.useEffect(() => {
|
|
20
|
+
if (!organizationId) return;
|
|
21
|
+
getOrganizationAction(organizationId).then(setOrganization);
|
|
22
|
+
}, [organizationId]);
|
|
16
23
|
|
|
17
|
-
|
|
24
|
+
if (loading || !impersonator || !user) return null;
|
|
18
25
|
|
|
19
26
|
return (
|
|
20
27
|
<div
|
package/src/cookie.ts
CHANGED
|
@@ -1,18 +1,39 @@
|
|
|
1
1
|
import { WORKOS_REDIRECT_URI, WORKOS_COOKIE_MAX_AGE, WORKOS_COOKIE_DOMAIN } from './env-variables.js';
|
|
2
2
|
import { CookieOptions } from './interfaces.js';
|
|
3
3
|
|
|
4
|
-
export function getCookieOptions(
|
|
4
|
+
export function getCookieOptions(): CookieOptions;
|
|
5
|
+
export function getCookieOptions(redirectUri?: string | null): CookieOptions;
|
|
6
|
+
export function getCookieOptions(redirectUri: string | null | undefined, asString: true, expired?: boolean): string;
|
|
7
|
+
export function getCookieOptions(
|
|
8
|
+
redirectUri: string | null | undefined,
|
|
9
|
+
asString: false,
|
|
10
|
+
expired?: boolean,
|
|
11
|
+
): CookieOptions;
|
|
12
|
+
export function getCookieOptions(
|
|
13
|
+
redirectUri?: string | null,
|
|
14
|
+
asString?: boolean,
|
|
15
|
+
expired?: boolean,
|
|
16
|
+
): CookieOptions | string;
|
|
17
|
+
export function getCookieOptions(
|
|
18
|
+
redirectUri?: string | null,
|
|
19
|
+
asString: boolean = false,
|
|
20
|
+
expired: boolean = false,
|
|
21
|
+
): CookieOptions | string {
|
|
5
22
|
const url = new URL(redirectUri || WORKOS_REDIRECT_URI);
|
|
6
23
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
24
|
+
const maxAge = expired ? 0 : WORKOS_COOKIE_MAX_AGE ? parseInt(WORKOS_COOKIE_MAX_AGE, 10) : 60 * 60 * 24 * 400;
|
|
25
|
+
|
|
26
|
+
return asString
|
|
27
|
+
? `Path=/; HttpOnly; Secure=${url.protocol === 'https:'}; SameSite="Lax"; Max-Age=${maxAge}; Domain=${WORKOS_COOKIE_DOMAIN || ''}`
|
|
28
|
+
: {
|
|
29
|
+
path: '/',
|
|
30
|
+
httpOnly: true,
|
|
31
|
+
secure: url.protocol === 'https:',
|
|
32
|
+
sameSite: 'lax' as const,
|
|
33
|
+
// Defaults to 400 days, the maximum allowed by Chrome
|
|
34
|
+
// It's fine to have a long cookie expiry date as the access/refresh tokens
|
|
35
|
+
// act as the actual time-limited aspects of the session.
|
|
36
|
+
maxAge,
|
|
37
|
+
domain: WORKOS_COOKIE_DOMAIN || '',
|
|
38
|
+
};
|
|
18
39
|
}
|
package/src/env-variables.ts
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
|
+
/* istanbul ignore file */
|
|
2
|
+
|
|
1
3
|
function getEnvVariable(name: string): string | undefined {
|
|
2
4
|
return process.env[name];
|
|
3
5
|
}
|
|
4
6
|
|
|
7
|
+
// Optional env variables
|
|
5
8
|
const WORKOS_API_HOSTNAME = getEnvVariable('WORKOS_API_HOSTNAME');
|
|
6
9
|
const WORKOS_API_HTTPS = getEnvVariable('WORKOS_API_HTTPS');
|
|
7
|
-
const WORKOS_API_KEY = getEnvVariable('WORKOS_API_KEY') ?? '';
|
|
8
10
|
const WORKOS_API_PORT = getEnvVariable('WORKOS_API_PORT');
|
|
9
|
-
const WORKOS_CLIENT_ID = getEnvVariable('WORKOS_CLIENT_ID') ?? '';
|
|
10
11
|
const WORKOS_COOKIE_DOMAIN = getEnvVariable('WORKOS_COOKIE_DOMAIN');
|
|
11
12
|
const WORKOS_COOKIE_MAX_AGE = getEnvVariable('WORKOS_COOKIE_MAX_AGE');
|
|
12
13
|
const WORKOS_COOKIE_NAME = getEnvVariable('WORKOS_COOKIE_NAME');
|
|
14
|
+
|
|
15
|
+
// Required env variables
|
|
16
|
+
const WORKOS_API_KEY = getEnvVariable('WORKOS_API_KEY') ?? '';
|
|
17
|
+
const WORKOS_CLIENT_ID = getEnvVariable('WORKOS_CLIENT_ID') ?? '';
|
|
13
18
|
const WORKOS_COOKIE_PASSWORD = getEnvVariable('WORKOS_COOKIE_PASSWORD') ?? '';
|
|
14
19
|
const WORKOS_REDIRECT_URI = process.env.NEXT_PUBLIC_WORKOS_REDIRECT_URI ?? '';
|
|
15
20
|
|
package/src/index.ts
CHANGED
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
import { handleAuth } from './authkit-callback-route.js';
|
|
2
|
-
import { authkitMiddleware } from './middleware.js';
|
|
3
|
-
import { withAuth, refreshSession
|
|
2
|
+
import { authkit, authkitMiddleware } from './middleware.js';
|
|
3
|
+
import { withAuth, refreshSession } from './session.js';
|
|
4
4
|
import { getSignInUrl, getSignUpUrl, signOut } from './auth.js';
|
|
5
|
-
import { Impersonation } from './impersonation.js';
|
|
6
|
-
import { AuthKitProvider } from './authkit-provider.js';
|
|
7
5
|
|
|
8
6
|
export {
|
|
9
7
|
handleAuth,
|
|
10
8
|
//
|
|
11
9
|
authkitMiddleware,
|
|
12
|
-
|
|
10
|
+
authkit,
|
|
13
11
|
//
|
|
14
12
|
getSignInUrl,
|
|
15
13
|
getSignUpUrl,
|
|
16
14
|
withAuth,
|
|
17
15
|
refreshSession,
|
|
18
16
|
signOut,
|
|
19
|
-
//
|
|
20
|
-
Impersonation,
|
|
21
|
-
AuthKitProvider,
|
|
22
17
|
};
|
package/src/interfaces.ts
CHANGED
|
@@ -37,6 +37,7 @@ export interface NoUserInfo {
|
|
|
37
37
|
organizationId?: undefined;
|
|
38
38
|
role?: undefined;
|
|
39
39
|
permissions?: undefined;
|
|
40
|
+
entitlements?: undefined;
|
|
40
41
|
impersonator?: undefined;
|
|
41
42
|
accessToken?: undefined;
|
|
42
43
|
}
|
|
@@ -68,6 +69,18 @@ export interface AuthkitMiddlewareOptions {
|
|
|
68
69
|
signUpPaths?: string[];
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
export interface AuthkitOptions {
|
|
73
|
+
debug?: boolean;
|
|
74
|
+
redirectUri?: string;
|
|
75
|
+
screenHint?: 'sign-up' | 'sign-in';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface AuthkitResponse {
|
|
79
|
+
session: UserInfo | NoUserInfo;
|
|
80
|
+
headers: Headers;
|
|
81
|
+
authorizationUrl?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
71
84
|
export interface CookieOptions {
|
|
72
85
|
path: '/';
|
|
73
86
|
httpOnly: true;
|
package/src/middleware.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { NextMiddleware } from 'next/server';
|
|
2
|
-
import { updateSession } from './session.js';
|
|
3
|
-
import { AuthkitMiddlewareOptions } from './interfaces.js';
|
|
1
|
+
import { NextMiddleware, NextRequest } from 'next/server';
|
|
2
|
+
import { updateSessionMiddleware, updateSession } from './session.js';
|
|
3
|
+
import { AuthkitMiddlewareOptions, AuthkitOptions, AuthkitResponse } from './interfaces.js';
|
|
4
4
|
import { WORKOS_REDIRECT_URI } from './env-variables.js';
|
|
5
5
|
|
|
6
6
|
export function authkitMiddleware({
|
|
@@ -10,6 +10,10 @@ export function authkitMiddleware({
|
|
|
10
10
|
signUpPaths = [],
|
|
11
11
|
}: AuthkitMiddlewareOptions = {}): NextMiddleware {
|
|
12
12
|
return function (request) {
|
|
13
|
-
return
|
|
13
|
+
return updateSessionMiddleware(request, debug, middlewareAuth, redirectUri, signUpPaths);
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
+
|
|
17
|
+
export async function authkit(request: NextRequest, options: AuthkitOptions = {}): Promise<AuthkitResponse> {
|
|
18
|
+
return await updateSession(request, options);
|
|
19
|
+
}
|