@absolutejs/auth 0.2.3 → 0.2.5
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/.prettierignore +4 -0
- package/.prettierrc.json +9 -0
- package/dist/index.js +1 -329
- package/package.json +3 -9
- package/src/authorize.ts +119 -0
- package/src/callback.ts +127 -0
- package/src/constants.ts +13 -0
- package/src/index.ts +88 -0
- package/src/logout.ts +39 -0
- package/src/protectRoute.ts +33 -0
- package/src/providers.ts +109 -0
- package/src/refresh.ts +63 -0
- package/src/revoke.ts +61 -0
- package/src/sessionStore.ts +10 -0
- package/src/status.ts +100 -0
- package/src/typeGuards.ts +32 -0
- package/src/types.ts +78 -0
- package/src/utils.ts +55 -0
- package/tsconfig.json +17 -0
- package/dist/example/components/AuthOptions.d.ts +0 -1
- package/dist/example/components/Example.d.ts +0 -1
- package/dist/example/components/Head.d.ts +0 -6
- package/dist/example/components/Modal.d.ts +0 -8
- package/dist/example/components/Navbar.d.ts +0 -10
- package/dist/example/components/NotAuthorized.d.ts +0 -1
- package/dist/example/components/Protected.d.ts +0 -1
- package/dist/example/db/migrate.d.ts +0 -1
- package/dist/example/db/schema.d.ts +0 -248
- package/dist/example/hooks/useAuthStatus.d.ts +0 -19
- package/dist/example/indexes/ExampleIndex.d.ts +0 -1
- package/dist/example/indexes/NotAuthorizedIndex.d.ts +0 -1
- package/dist/example/indexes/ProtectedIndex.d.ts +0 -1
- package/dist/example/server.d.ts +0 -1
- package/dist/example/utils/networking.d.ts +0 -1
- package/dist/example/utils/pageUtils.d.ts +0 -2
- package/dist/example/utils/styles.d.ts +0 -15
- package/dist/example/utils/userUtils.d.ts +0 -36
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.2.
|
|
2
|
+
"version": "0.2.5",
|
|
3
3
|
"name": "@absolutejs/auth",
|
|
4
4
|
"description": "An authorization library for absolutejs",
|
|
5
5
|
"repository": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"license": "CC BY-NC 4.0",
|
|
11
11
|
"author": "Alex Kahn",
|
|
12
12
|
"scripts": {
|
|
13
|
-
"build": "rm -rf dist && bun build src/index.ts --outdir dist --minify --splitting --target=bun && tsc --emitDeclarationOnly --project tsconfig.json",
|
|
13
|
+
"build": "rm -rf dist && bun build src/index.ts --outdir dist --minify --splitting --target=bun --external arctic --external elysia && tsc --emitDeclarationOnly --project tsconfig.json",
|
|
14
14
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
15
15
|
"format": "prettier --write \"./**/*.{js,jsx,ts,tsx,css,json}\"",
|
|
16
16
|
"dev": "bun run --watch example/server.ts",
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
"oauth"
|
|
28
28
|
],
|
|
29
29
|
"types": "dist/src/index.d.ts",
|
|
30
|
-
"dependencies": {},
|
|
31
30
|
"peerDependencies": {
|
|
32
31
|
"arctic": ">= 3.6.0",
|
|
33
32
|
"elysia": ">= 1.2.0"
|
|
@@ -47,10 +46,5 @@
|
|
|
47
46
|
"elysia": "1.2.25",
|
|
48
47
|
"typescript": "5.8.3"
|
|
49
48
|
},
|
|
50
|
-
"module": "dist/index.js"
|
|
51
|
-
"files": [
|
|
52
|
-
"dist/",
|
|
53
|
-
"README.md",
|
|
54
|
-
"LICENSE"
|
|
55
|
-
]
|
|
49
|
+
"module": "dist/index.js"
|
|
56
50
|
}
|
package/src/authorize.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { generateState, generateCodeVerifier } from 'arctic';
|
|
2
|
+
import { Elysia } from 'elysia';
|
|
3
|
+
import { isValidProviderKey } from './typeGuards';
|
|
4
|
+
import { ClientProviders } from './types';
|
|
5
|
+
import { COOKIE_DURATION } from './constants';
|
|
6
|
+
|
|
7
|
+
type AuthorizeProps = {
|
|
8
|
+
clientProviders: ClientProviders;
|
|
9
|
+
authorizeRoute?: string;
|
|
10
|
+
onAuthorize?: () => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const authorize = ({
|
|
14
|
+
clientProviders,
|
|
15
|
+
authorizeRoute = 'authorize',
|
|
16
|
+
onAuthorize
|
|
17
|
+
}: AuthorizeProps) =>
|
|
18
|
+
new Elysia().get(
|
|
19
|
+
`/${authorizeRoute}/:provider`,
|
|
20
|
+
({
|
|
21
|
+
error,
|
|
22
|
+
redirect,
|
|
23
|
+
cookie: { state, code_verifier, auth_provider, redirect_url },
|
|
24
|
+
params: { provider },
|
|
25
|
+
headers
|
|
26
|
+
}) => {
|
|
27
|
+
if (provider === undefined)
|
|
28
|
+
return error('Bad Request', 'Provider is required');
|
|
29
|
+
|
|
30
|
+
if (!isValidProviderKey(provider)) {
|
|
31
|
+
return error('Bad Request', 'Invalid provider');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const normalizedProvider = provider.toLowerCase();
|
|
36
|
+
const { providerInstance, scopes, searchParams } =
|
|
37
|
+
clientProviders[normalizedProvider];
|
|
38
|
+
|
|
39
|
+
const redirectUrl = headers['referer'] ?? '/';
|
|
40
|
+
|
|
41
|
+
redirect_url.set({
|
|
42
|
+
httpOnly: true,
|
|
43
|
+
maxAge: COOKIE_DURATION,
|
|
44
|
+
path: '/',
|
|
45
|
+
sameSite: 'lax',
|
|
46
|
+
secure: true,
|
|
47
|
+
value: redirectUrl
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
auth_provider.set({
|
|
51
|
+
httpOnly: true,
|
|
52
|
+
maxAge: COOKIE_DURATION,
|
|
53
|
+
path: '/',
|
|
54
|
+
sameSite: 'lax',
|
|
55
|
+
secure: true,
|
|
56
|
+
value: provider
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const currentState = generateState();
|
|
60
|
+
|
|
61
|
+
state.set({
|
|
62
|
+
httpOnly: true,
|
|
63
|
+
maxAge: COOKIE_DURATION,
|
|
64
|
+
path: '/',
|
|
65
|
+
sameSite: 'lax',
|
|
66
|
+
secure: true,
|
|
67
|
+
value: currentState
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const usesCodeVerifier = providerInstance.createAuthorizationURL
|
|
71
|
+
.toString()
|
|
72
|
+
.includes('codeVerifier');
|
|
73
|
+
const verifier = usesCodeVerifier
|
|
74
|
+
? generateCodeVerifier()
|
|
75
|
+
: undefined;
|
|
76
|
+
void (
|
|
77
|
+
usesCodeVerifier &&
|
|
78
|
+
code_verifier.set({
|
|
79
|
+
httpOnly: true,
|
|
80
|
+
maxAge: COOKIE_DURATION,
|
|
81
|
+
path: '/',
|
|
82
|
+
sameSite: 'lax',
|
|
83
|
+
secure: true,
|
|
84
|
+
value: verifier ?? ''
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const authorizationURL = usesCodeVerifier
|
|
89
|
+
? providerInstance.createAuthorizationURL(
|
|
90
|
+
currentState,
|
|
91
|
+
// @ts-expect-error - This is a dynamic check
|
|
92
|
+
verifier,
|
|
93
|
+
scopes
|
|
94
|
+
)
|
|
95
|
+
: // @ts-expect-error - This is a dynamic check
|
|
96
|
+
providerInstance.createAuthorizationURL(
|
|
97
|
+
currentState,
|
|
98
|
+
scopes
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
searchParams.forEach(([key, value]) => {
|
|
102
|
+
authorizationURL.searchParams.set(key, value);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
onAuthorize?.();
|
|
106
|
+
|
|
107
|
+
return redirect(authorizationURL.toString());
|
|
108
|
+
} catch (err) {
|
|
109
|
+
if (err instanceof Error) {
|
|
110
|
+
return error(
|
|
111
|
+
'Internal Server Error',
|
|
112
|
+
`${err.message} - ${err.stack ?? ''}`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return error('Internal Server Error', `Unknown error: ${err}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
);
|
package/src/callback.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { decodeIdToken } from 'arctic';
|
|
2
|
+
import { Elysia } from 'elysia';
|
|
3
|
+
import { sessionStore } from './sessionStore';
|
|
4
|
+
import { isNonEmptyString, isValidProviderKey } from './typeGuards';
|
|
5
|
+
import { ClientProviders, OnCallback } from './types';
|
|
6
|
+
|
|
7
|
+
type CallbackProps<UserType> = {
|
|
8
|
+
clientProviders: ClientProviders;
|
|
9
|
+
callbackRoute?: string;
|
|
10
|
+
onCallback?: OnCallback<UserType>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const callback = <UserType>({
|
|
14
|
+
clientProviders,
|
|
15
|
+
callbackRoute = 'authorize/callback',
|
|
16
|
+
onCallback
|
|
17
|
+
}: CallbackProps<UserType>) =>
|
|
18
|
+
new Elysia()
|
|
19
|
+
.use(sessionStore<UserType>())
|
|
20
|
+
.get(
|
|
21
|
+
`/${callbackRoute}`,
|
|
22
|
+
async ({
|
|
23
|
+
error,
|
|
24
|
+
redirect,
|
|
25
|
+
store: { session },
|
|
26
|
+
cookie: {
|
|
27
|
+
state: stored_state,
|
|
28
|
+
code_verifier,
|
|
29
|
+
redirect_url,
|
|
30
|
+
user_session_id,
|
|
31
|
+
auth_provider
|
|
32
|
+
},
|
|
33
|
+
query: { code, state: callback_state }
|
|
34
|
+
}) => {
|
|
35
|
+
if (
|
|
36
|
+
!isNonEmptyString(code) ||
|
|
37
|
+
stored_state.value === undefined
|
|
38
|
+
) {
|
|
39
|
+
return error('Bad Request', 'Invalid callback request');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (callback_state !== stored_state.value) {
|
|
43
|
+
return error('Bad Request', 'Invalid state mismatch');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (auth_provider.value === undefined) {
|
|
47
|
+
return error('Unauthorized', 'No auth provider found');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!isValidProviderKey(auth_provider.value)) {
|
|
51
|
+
return error('Unauthorized', 'Invalid provider');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const normalizedProvider = auth_provider.value.toLowerCase();
|
|
55
|
+
const { providerInstance } =
|
|
56
|
+
clientProviders[normalizedProvider];
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// Clear the stored state so the next request doesn't use it
|
|
60
|
+
stored_state.remove();
|
|
61
|
+
|
|
62
|
+
const hasCodeVerifier =
|
|
63
|
+
providerInstance.validateAuthorizationCode
|
|
64
|
+
.toString()
|
|
65
|
+
.includes('codeVerifier');
|
|
66
|
+
const verifier = code_verifier.value;
|
|
67
|
+
if (hasCodeVerifier && verifier === undefined)
|
|
68
|
+
return error(
|
|
69
|
+
'Bad Request',
|
|
70
|
+
'Code verifier not found and is required'
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const safeVerifier = verifier ?? '';
|
|
74
|
+
const tokens = await (hasCodeVerifier
|
|
75
|
+
? providerInstance.validateAuthorizationCode(
|
|
76
|
+
code,
|
|
77
|
+
safeVerifier
|
|
78
|
+
)
|
|
79
|
+
: // @ts-expect-error - This is a dynamic check
|
|
80
|
+
providerInstance.validateAuthorizationCode(code));
|
|
81
|
+
if (hasCodeVerifier) code_verifier.remove();
|
|
82
|
+
|
|
83
|
+
const decodedIdToken = Object.fromEntries(
|
|
84
|
+
Object.entries(decodeIdToken(tokens.idToken())).map(
|
|
85
|
+
([key, value]) => [
|
|
86
|
+
key,
|
|
87
|
+
typeof value === 'string' ? value : undefined
|
|
88
|
+
]
|
|
89
|
+
)
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const authProvider = auth_provider.value;
|
|
93
|
+
|
|
94
|
+
console.log(
|
|
95
|
+
'in route user_session_id before',
|
|
96
|
+
user_session_id.value
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
await onCallback?.({
|
|
100
|
+
authProvider,
|
|
101
|
+
decodedIdToken,
|
|
102
|
+
session,
|
|
103
|
+
user_session_id
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
console.log(
|
|
107
|
+
'in route user_session_id after',
|
|
108
|
+
user_session_id.value
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const redirectUrl = redirect_url.value ?? '/';
|
|
112
|
+
|
|
113
|
+
return redirect(redirectUrl);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
if (err instanceof Error)
|
|
116
|
+
return error(
|
|
117
|
+
'Internal Server Error',
|
|
118
|
+
`${err.message} - ${err.stack ?? ''}`
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
return error(
|
|
122
|
+
'Internal Server Error',
|
|
123
|
+
`Failed to validate authorization code: Unknown error: ${err}`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
);
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const SECONDS_IN_A_MINUTE = 60;
|
|
2
|
+
export const MILLISECONDS_IN_A_SECOND = 1000;
|
|
3
|
+
export const MILLISECONDS_IN_A_MINUTE =
|
|
4
|
+
MILLISECONDS_IN_A_SECOND * SECONDS_IN_A_MINUTE;
|
|
5
|
+
export const MINUTES_IN_AN_HOUR = 60;
|
|
6
|
+
export const HOURS_IN_A_DAY = 24;
|
|
7
|
+
export const MILLISECONDS_IN_A_DAY =
|
|
8
|
+
MILLISECONDS_IN_A_SECOND *
|
|
9
|
+
SECONDS_IN_A_MINUTE *
|
|
10
|
+
MINUTES_IN_AN_HOUR *
|
|
11
|
+
HOURS_IN_A_DAY;
|
|
12
|
+
export const COOKIE_MINUTES = 30; // TODO : See if i can remove
|
|
13
|
+
export const COOKIE_DURATION = SECONDS_IN_A_MINUTE * COOKIE_MINUTES;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { OAuth2RequestError, ArcticFetchError } from 'arctic';
|
|
2
|
+
import { Elysia } from 'elysia';
|
|
3
|
+
import { authorize } from './authorize';
|
|
4
|
+
import { callback } from './callback';
|
|
5
|
+
import { logout } from './logout';
|
|
6
|
+
import { protectRoute } from './protectRoute';
|
|
7
|
+
import { normalizedProviderKeys, providers } from './providers';
|
|
8
|
+
import { refresh } from './refresh';
|
|
9
|
+
import { revoke } from './revoke';
|
|
10
|
+
import { status } from './status';
|
|
11
|
+
import { isValidProviderKey } from './typeGuards';
|
|
12
|
+
import { AbsoluteAuthProps, ClientProviders } from './types';
|
|
13
|
+
|
|
14
|
+
export const absoluteAuth = <UserType>({
|
|
15
|
+
config,
|
|
16
|
+
authorizeRoute,
|
|
17
|
+
callbackRoute,
|
|
18
|
+
logoutRoute,
|
|
19
|
+
statusRoute,
|
|
20
|
+
refreshRoute,
|
|
21
|
+
revokeRoute,
|
|
22
|
+
onAuthorize,
|
|
23
|
+
onCallback,
|
|
24
|
+
onStatus,
|
|
25
|
+
onRefresh,
|
|
26
|
+
onLogout,
|
|
27
|
+
onRevoke
|
|
28
|
+
}: AbsoluteAuthProps<UserType>) => {
|
|
29
|
+
const clientProviders = Object.keys(config).reduce<ClientProviders>(
|
|
30
|
+
(acc, key) => {
|
|
31
|
+
if (!Object.prototype.hasOwnProperty.call(config, key)) return acc;
|
|
32
|
+
|
|
33
|
+
if (!isValidProviderKey(key)) {
|
|
34
|
+
console.error(`Provider ${key} is not supported`);
|
|
35
|
+
|
|
36
|
+
return acc;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const options = config[key];
|
|
40
|
+
if (!options) return acc;
|
|
41
|
+
|
|
42
|
+
const normalizedProvider = key.toLowerCase();
|
|
43
|
+
const originalProviderKey =
|
|
44
|
+
normalizedProviderKeys[normalizedProvider];
|
|
45
|
+
|
|
46
|
+
if (!isValidProviderKey(originalProviderKey)) {
|
|
47
|
+
console.error(`Provider ${key} is not supported`);
|
|
48
|
+
|
|
49
|
+
return acc;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const Provider = providers[originalProviderKey];
|
|
53
|
+
const { credentials, scopes = [], searchParams = [] } = options;
|
|
54
|
+
|
|
55
|
+
// @ts-expect-error: dynamic constructor parameters
|
|
56
|
+
const providerInstance = new Provider(...credentials);
|
|
57
|
+
|
|
58
|
+
acc[normalizedProvider] = {
|
|
59
|
+
providerInstance,
|
|
60
|
+
scopes,
|
|
61
|
+
searchParams
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return acc;
|
|
65
|
+
},
|
|
66
|
+
{}
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
return new Elysia()
|
|
70
|
+
.error('OAUTH2_REQUEST_ERROR', OAuth2RequestError)
|
|
71
|
+
.error('ARCTIC_FETCH_ERROR', ArcticFetchError)
|
|
72
|
+
.use(logout({ logoutRoute, onLogout }))
|
|
73
|
+
.use(revoke({ clientProviders, onRevoke, revokeRoute }))
|
|
74
|
+
.use(status<UserType>({ clientProviders, onStatus, statusRoute }))
|
|
75
|
+
.use(refresh({ clientProviders, onRefresh, refreshRoute }))
|
|
76
|
+
.use(authorize({ authorizeRoute, clientProviders, onAuthorize }))
|
|
77
|
+
.use(
|
|
78
|
+
callback<UserType>({
|
|
79
|
+
callbackRoute,
|
|
80
|
+
clientProviders,
|
|
81
|
+
onCallback
|
|
82
|
+
})
|
|
83
|
+
)
|
|
84
|
+
.use(protectRoute())
|
|
85
|
+
.as('plugin');
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export * from './utils';
|
package/src/logout.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Elysia } from 'elysia';
|
|
2
|
+
|
|
3
|
+
type LogoutProps = {
|
|
4
|
+
logoutRoute?: string;
|
|
5
|
+
onLogout?: () => void;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const logout = ({ logoutRoute = 'logout', onLogout }: LogoutProps) =>
|
|
9
|
+
new Elysia().post(
|
|
10
|
+
`/${logoutRoute}`,
|
|
11
|
+
async ({ error, cookie: { user_session_id, auth_provider } }) => {
|
|
12
|
+
if (auth_provider.value === undefined) {
|
|
13
|
+
return error('Unauthorized', 'No auth provider found');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
onLogout?.();
|
|
18
|
+
|
|
19
|
+
user_session_id.remove();
|
|
20
|
+
auth_provider.remove();
|
|
21
|
+
|
|
22
|
+
return new Response('Succesfuly Logged Out', {
|
|
23
|
+
status: 204
|
|
24
|
+
});
|
|
25
|
+
} catch (err) {
|
|
26
|
+
if (err instanceof Error) {
|
|
27
|
+
return error(
|
|
28
|
+
'Internal Server Error',
|
|
29
|
+
`Failed to logout: ${err.message}`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return error(
|
|
34
|
+
'Internal Server Error',
|
|
35
|
+
`Failed to logout: Unknown error: ${err}`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Elysia } from 'elysia';
|
|
2
|
+
import { sessionStore } from './sessionStore';
|
|
3
|
+
|
|
4
|
+
export const protectRoute = <UserType>() =>
|
|
5
|
+
new Elysia()
|
|
6
|
+
.use(sessionStore<UserType>())
|
|
7
|
+
.derive(
|
|
8
|
+
({ store: { session }, cookie: { user_session_id }, error }) => ({
|
|
9
|
+
protectRoute: async (
|
|
10
|
+
handleAuth: () => Promise<Response>,
|
|
11
|
+
handleAuthFail?: () => Promise<Response>
|
|
12
|
+
) => {
|
|
13
|
+
if (user_session_id.value === undefined) {
|
|
14
|
+
return (
|
|
15
|
+
handleAuthFail?.() ??
|
|
16
|
+
error('Unauthorized', 'No session ID found')
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const userSession = session[user_session_id.value];
|
|
21
|
+
|
|
22
|
+
if (userSession === undefined) {
|
|
23
|
+
return (
|
|
24
|
+
handleAuthFail?.() ??
|
|
25
|
+
error('Unauthorized', 'No session found')
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return handleAuth();
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
)
|
|
33
|
+
.as('plugin');
|
package/src/providers.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AmazonCognito,
|
|
3
|
+
AniList,
|
|
4
|
+
Apple,
|
|
5
|
+
Atlassian,
|
|
6
|
+
Auth0,
|
|
7
|
+
Authentik,
|
|
8
|
+
Bitbucket,
|
|
9
|
+
Box,
|
|
10
|
+
Coinbase,
|
|
11
|
+
Discord,
|
|
12
|
+
Dribbble,
|
|
13
|
+
Dropbox,
|
|
14
|
+
Facebook,
|
|
15
|
+
Figma,
|
|
16
|
+
Intuit,
|
|
17
|
+
GitHub,
|
|
18
|
+
GitLab,
|
|
19
|
+
Google,
|
|
20
|
+
Kakao,
|
|
21
|
+
KeyCloak,
|
|
22
|
+
Lichess,
|
|
23
|
+
Line,
|
|
24
|
+
Linear,
|
|
25
|
+
LinkedIn,
|
|
26
|
+
MicrosoftEntraId,
|
|
27
|
+
MyAnimeList,
|
|
28
|
+
Notion,
|
|
29
|
+
Okta,
|
|
30
|
+
Osu,
|
|
31
|
+
Patreon,
|
|
32
|
+
Reddit,
|
|
33
|
+
Roblox,
|
|
34
|
+
Salesforce,
|
|
35
|
+
Shikimori,
|
|
36
|
+
Slack,
|
|
37
|
+
Spotify,
|
|
38
|
+
Strava,
|
|
39
|
+
Tiltify,
|
|
40
|
+
Tumblr,
|
|
41
|
+
Twitch,
|
|
42
|
+
Twitter,
|
|
43
|
+
VK,
|
|
44
|
+
WorkOS,
|
|
45
|
+
Yahoo,
|
|
46
|
+
Yandex,
|
|
47
|
+
Zoom,
|
|
48
|
+
FortyTwo
|
|
49
|
+
} from 'arctic';
|
|
50
|
+
|
|
51
|
+
// TODO: When arctic adds better way to get the providers give a type to the object
|
|
52
|
+
// eslint-disable-next-line custom/explicit-object-types
|
|
53
|
+
export const providers = {
|
|
54
|
+
AmazonCognito,
|
|
55
|
+
AniList,
|
|
56
|
+
Apple,
|
|
57
|
+
Atlassian,
|
|
58
|
+
Auth0,
|
|
59
|
+
Authentik,
|
|
60
|
+
Bitbucket,
|
|
61
|
+
Box,
|
|
62
|
+
Coinbase,
|
|
63
|
+
Discord,
|
|
64
|
+
Dribbble,
|
|
65
|
+
Dropbox,
|
|
66
|
+
Facebook,
|
|
67
|
+
Figma,
|
|
68
|
+
FortyTwo,
|
|
69
|
+
GitHub,
|
|
70
|
+
GitLab,
|
|
71
|
+
Google,
|
|
72
|
+
Intuit,
|
|
73
|
+
Kakao,
|
|
74
|
+
KeyCloak,
|
|
75
|
+
Lichess,
|
|
76
|
+
Line,
|
|
77
|
+
Linear,
|
|
78
|
+
LinkedIn,
|
|
79
|
+
MicrosoftEntraId,
|
|
80
|
+
MyAnimeList,
|
|
81
|
+
Notion,
|
|
82
|
+
Okta,
|
|
83
|
+
Osu,
|
|
84
|
+
Patreon,
|
|
85
|
+
Reddit,
|
|
86
|
+
Roblox,
|
|
87
|
+
Salesforce,
|
|
88
|
+
Shikimori,
|
|
89
|
+
Slack,
|
|
90
|
+
Spotify,
|
|
91
|
+
Strava,
|
|
92
|
+
Tiltify,
|
|
93
|
+
Tumblr,
|
|
94
|
+
Twitch,
|
|
95
|
+
Twitter,
|
|
96
|
+
VK,
|
|
97
|
+
WorkOS,
|
|
98
|
+
Yahoo,
|
|
99
|
+
Yandex,
|
|
100
|
+
Zoom
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const normalizedProviderKeys = Object.keys(providers).reduce<
|
|
104
|
+
Record<string, string>
|
|
105
|
+
>((map, key) => {
|
|
106
|
+
map[key.toLowerCase()] = key;
|
|
107
|
+
|
|
108
|
+
return map;
|
|
109
|
+
}, {});
|
package/src/refresh.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Elysia } from 'elysia';
|
|
2
|
+
import { isRefreshableProvider } from './typeGuards';
|
|
3
|
+
import { ClientProviders } from './types';
|
|
4
|
+
|
|
5
|
+
type RefreshProps = {
|
|
6
|
+
clientProviders: ClientProviders;
|
|
7
|
+
refreshRoute?: string;
|
|
8
|
+
onRefresh?: () => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const refresh = ({
|
|
12
|
+
clientProviders,
|
|
13
|
+
refreshRoute = 'refresh',
|
|
14
|
+
onRefresh
|
|
15
|
+
}: RefreshProps) =>
|
|
16
|
+
new Elysia().post(
|
|
17
|
+
`/${refreshRoute}`,
|
|
18
|
+
async ({ error, cookie: { user_refresh_token, auth_provider } }) => {
|
|
19
|
+
if (user_refresh_token.value === undefined) {
|
|
20
|
+
return error('Unauthorized', 'No refresh token found');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (auth_provider.value === undefined) {
|
|
24
|
+
return error('Unauthorized', 'No auth provider found');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const normalizedProvider = auth_provider.value.toLowerCase();
|
|
28
|
+
const { providerInstance } = clientProviders[normalizedProvider];
|
|
29
|
+
|
|
30
|
+
if (!isRefreshableProvider(providerInstance)) {
|
|
31
|
+
return error('Not Implemented', 'Provider is not refreshable');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
//consider passing tokens to onRefresh
|
|
36
|
+
// const tokens = await providerInstance.refreshAccessToken(
|
|
37
|
+
// user_refresh_token.value
|
|
38
|
+
// );
|
|
39
|
+
|
|
40
|
+
await providerInstance.refreshAccessToken(
|
|
41
|
+
user_refresh_token.value
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
onRefresh?.();
|
|
45
|
+
|
|
46
|
+
return new Response('Token refreshed', {
|
|
47
|
+
status: 204
|
|
48
|
+
});
|
|
49
|
+
} catch (err) {
|
|
50
|
+
if (err instanceof Error) {
|
|
51
|
+
return error(
|
|
52
|
+
'Internal Server Error',
|
|
53
|
+
`Failed to refresh token: ${err.message}`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return error(
|
|
58
|
+
'Internal Server Error',
|
|
59
|
+
`Faile to refresh token: Unknown error: ${err}`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
);
|
package/src/revoke.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Elysia } from 'elysia';
|
|
2
|
+
import { isRevocableProvider } from './typeGuards';
|
|
3
|
+
import { ClientProviders } from './types';
|
|
4
|
+
|
|
5
|
+
type RevokeProps = {
|
|
6
|
+
clientProviders: ClientProviders;
|
|
7
|
+
revokeRoute?: string;
|
|
8
|
+
onRevoke?: () => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const revoke = ({
|
|
12
|
+
clientProviders,
|
|
13
|
+
revokeRoute = 'revoke',
|
|
14
|
+
onRevoke
|
|
15
|
+
}: RevokeProps) =>
|
|
16
|
+
new Elysia().post(
|
|
17
|
+
`/${revokeRoute}/access-token`,
|
|
18
|
+
async ({ error, cookie: { user_refresh_token, auth_provider } }) => {
|
|
19
|
+
if (user_refresh_token.value === undefined) {
|
|
20
|
+
return error('Unauthorized', 'No refresh token found');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (auth_provider.value === undefined) {
|
|
24
|
+
return error('Unauthorized', 'No auth provider found');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const normalizedProvider = auth_provider.value.toLowerCase();
|
|
28
|
+
const { providerInstance } = clientProviders[normalizedProvider];
|
|
29
|
+
|
|
30
|
+
if (!isRevocableProvider(providerInstance)) {
|
|
31
|
+
return error(
|
|
32
|
+
'Not Implemented',
|
|
33
|
+
'Provider does not support revocation'
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
await providerInstance.revokeAccessToken(
|
|
39
|
+
user_refresh_token.value
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
onRevoke?.();
|
|
43
|
+
|
|
44
|
+
return new Response('Token revoked', {
|
|
45
|
+
status: 204
|
|
46
|
+
});
|
|
47
|
+
} catch (err) {
|
|
48
|
+
if (err instanceof Error) {
|
|
49
|
+
return error(
|
|
50
|
+
'Internal Server Error',
|
|
51
|
+
`Failed to revoke token: ${err.message}`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return error(
|
|
56
|
+
'Internal Server Error',
|
|
57
|
+
`Failed to revoke token: Unknown error: ${err}`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Elysia } from 'elysia';
|
|
2
|
+
import { SessionRecord } from './types';
|
|
3
|
+
|
|
4
|
+
export const sessionStore = <UserType>() => {
|
|
5
|
+
const initialSession: SessionRecord<UserType> = {};
|
|
6
|
+
|
|
7
|
+
return new Elysia({ name: 'sessionStore' }).state({
|
|
8
|
+
session: initialSession
|
|
9
|
+
});
|
|
10
|
+
};
|