@absolutejs/auth 0.4.0 → 0.5.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/src/index.ts DELETED
@@ -1,88 +0,0 @@
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 DELETED
@@ -1,39 +0,0 @@
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
- );
package/src/profiles.ts DELETED
@@ -1,322 +0,0 @@
1
- export type ProfileRequest = {
2
- endpoint: string;
3
- method?: 'GET' | 'POST';
4
- authIn: 'header' | 'query';
5
- tokenParam?: string;
6
- headers?: Record<string, string>;
7
- body?: any;
8
- };
9
-
10
- export async function fetchUserProfile(provider: string, accessToken: string) {
11
- const cfg: ProfileRequest | undefined = profileConfigs[provider];
12
- if (!cfg) {
13
- throw new Error(`Unknown provider: ${provider}`);
14
- }
15
-
16
- let url = cfg.endpoint;
17
- const method = cfg.method ?? 'GET';
18
- const headers: Record<string, string> = {};
19
-
20
- // Merge any static headers first
21
- if (cfg.headers) {
22
- Object.assign(headers, cfg.headers);
23
- }
24
-
25
- // Place token in header or query
26
- if (cfg.authIn === 'header') {
27
- headers['Authorization'] = `Bearer ${accessToken}`;
28
- } else {
29
- const tokenKey = cfg.tokenParam ?? 'access_token';
30
- const sep = url.includes('?') ? '&' : '?';
31
- url = `${url}${sep}${tokenKey}=${encodeURIComponent(accessToken)}`;
32
- }
33
-
34
- const init: RequestInit = { method, headers };
35
-
36
- if (method === 'POST' && cfg.body) {
37
- headers['Content-Type'] = 'application/json';
38
- init.body = JSON.stringify(cfg.body);
39
- }
40
-
41
- const res = await fetch(url, init);
42
- if (!res.ok) {
43
- const errText = await res.text();
44
- throw new Error(
45
- `Failed to fetch ${provider} profile: ${res.status} ${errText}`
46
- );
47
- }
48
-
49
- return res.json();
50
- }
51
-
52
- export const profileConfigs: Record<string, ProfileRequest> = {
53
- facebook: {
54
- endpoint: 'https://graph.facebook.com/me?fields=id,name,picture,email',
55
- method: 'GET',
56
- authIn: 'query',
57
- tokenParam: 'access_token'
58
- },
59
- anilist: {
60
- endpoint: 'https://graphql.anilist.co',
61
- method: 'POST',
62
- authIn: 'header',
63
- headers: {
64
- 'Content-Type': 'application/json',
65
- Accept: 'application/json'
66
- },
67
- body: {
68
- query: `query { Viewer { id name } }`
69
- }
70
- },
71
- atlassian: {
72
- endpoint: 'https://api.atlassian.com/me',
73
- method: 'GET',
74
- authIn: 'header'
75
- },
76
- battlenet: {
77
- endpoint: 'https://oauth.battle.net/userinfo',
78
- method: 'GET',
79
- authIn: 'header'
80
- },
81
- bitbucket: {
82
- endpoint: 'https://api.bitbucket.org/2.0/user',
83
- method: 'GET',
84
- authIn: 'header'
85
- },
86
- box: {
87
- endpoint: 'https://api.box.com/2.0/users/me',
88
- method: 'GET',
89
- authIn: 'header'
90
- },
91
- bungie: {
92
- endpoint:
93
- 'https://www.bungie.net/Platform/User/GetCurrentBungieNetUser',
94
- method: 'GET',
95
- authIn: 'header',
96
- headers: {
97
- 'X-API-Key': '<YOUR_API_KEY>'
98
- }
99
- },
100
- coinbase: {
101
- endpoint: 'https://api.coinbase.com/v2/user',
102
- method: 'GET',
103
- authIn: 'header'
104
- },
105
- discord: {
106
- endpoint: 'https://discord.com/api/users/@me',
107
- method: 'GET',
108
- authIn: 'header'
109
- },
110
- donationAlerts: {
111
- endpoint: 'https://www.donationalerts.com/api/v1/user',
112
- method: 'GET',
113
- authIn: 'header'
114
- },
115
- dribbble: {
116
- endpoint: 'https://api.dribbble.com/v2/user',
117
- method: 'GET',
118
- authIn: 'header'
119
- },
120
- dropbox: {
121
- endpoint: 'https://api.dropboxapi.com/2/users/get_current_account',
122
- method: 'GET',
123
- authIn: 'header'
124
- },
125
- epicGames: {
126
- endpoint: 'https://api.epicgames.dev/epic/oauth/v2/userInfo',
127
- method: 'GET',
128
- authIn: 'header'
129
- },
130
- etsy: {
131
- endpoint: 'https://openapi.etsy.com/v3/application/users/me',
132
- method: 'GET',
133
- authIn: 'header'
134
- },
135
- figma: {
136
- endpoint: 'https://api.figma.com/v1/me',
137
- method: 'GET',
138
- authIn: 'header'
139
- },
140
- gitea: {
141
- endpoint: 'https://<YOUR_GITEA_DOMAIN>/api/v1/user',
142
- method: 'GET',
143
- authIn: 'header'
144
- },
145
- github: {
146
- endpoint: 'https://api.github.com/user',
147
- method: 'GET',
148
- authIn: 'header'
149
- },
150
- gitlab: {
151
- endpoint: 'https://gitlab.com/api/v4/user',
152
- method: 'GET',
153
- authIn: 'header'
154
- },
155
- intuit: {
156
- endpoint: 'https://oauth.platform.intuit.com/oauth2/v1/userinfo',
157
- method: 'GET',
158
- authIn: 'header'
159
- },
160
- kakao: {
161
- endpoint: 'https://kapi.kakao.com/v2/user/me',
162
- method: 'GET',
163
- authIn: 'header'
164
- },
165
- kick: {
166
- endpoint: 'https://api.kick.com/v1/user',
167
- method: 'GET',
168
- authIn: 'header'
169
- },
170
- lichess: {
171
- endpoint: 'https://lichess.org/api/account',
172
- method: 'GET',
173
- authIn: 'header'
174
- },
175
- line: {
176
- endpoint: 'https://api.line.me/v2/profile',
177
- method: 'GET',
178
- authIn: 'header'
179
- },
180
- linear: {
181
- endpoint: 'https://api.linear.app/graphql',
182
- method: 'POST',
183
- authIn: 'header',
184
- headers: {
185
- 'Content-Type': 'application/json',
186
- Accept: 'application/json'
187
- },
188
- body: {
189
- query: `query { viewer { id name } }`
190
- }
191
- },
192
- mastodon: {
193
- endpoint: 'https://<YOUR_INSTANCE>/api/v1/accounts/verify_credentials',
194
- method: 'GET',
195
- authIn: 'header'
196
- },
197
- mercadoLibre: {
198
- endpoint: 'https://api.mercadolibre.com/users/me',
199
- method: 'GET',
200
- authIn: 'header'
201
- },
202
- mercadoPago: {
203
- endpoint: 'https://api.mercadopago.com/v1/users/me',
204
- method: 'GET',
205
- authIn: 'header'
206
- },
207
- myAnimeList: {
208
- endpoint: 'https://api.myanimelist.net/v2/users/@me',
209
- method: 'GET',
210
- authIn: 'header'
211
- },
212
- naver: {
213
- endpoint: 'https://openapi.naver.com/v1/nid/me',
214
- method: 'GET',
215
- authIn: 'header'
216
- },
217
- notion: {
218
- endpoint: 'https://api.notion.com/v1/users/me',
219
- method: 'GET',
220
- authIn: 'header'
221
- },
222
- osu: {
223
- endpoint: 'https://osu.ppy.sh/api/v2/me',
224
- method: 'GET',
225
- authIn: 'header'
226
- },
227
- patreon: {
228
- endpoint: 'https://www.patreon.com/api/oauth2/v2/identity',
229
- method: 'GET',
230
- authIn: 'header'
231
- },
232
- polar: {
233
- endpoint: 'https://www.polaraccesslink.com/v3/users/<USER_ID>',
234
- method: 'GET',
235
- authIn: 'header'
236
- },
237
- reddit: {
238
- endpoint: 'https://oauth.reddit.com/api/v1/me',
239
- method: 'GET',
240
- authIn: 'header'
241
- },
242
- roblox: {
243
- endpoint: 'https://apis.roblox.com/oauth/v1/userinfo',
244
- method: 'GET',
245
- authIn: 'header'
246
- },
247
- shikimori: {
248
- endpoint: 'https://shikimori.one/api/users/whoami',
249
- method: 'GET',
250
- authIn: 'header'
251
- },
252
- slack: {
253
- endpoint: 'https://slack.com/api/users.identity',
254
- method: 'GET',
255
- authIn: 'query',
256
- tokenParam: 'token'
257
- },
258
- spotify: {
259
- endpoint: 'https://api.spotify.com/v1/me',
260
- method: 'GET',
261
- authIn: 'header'
262
- },
263
- startGg: {
264
- endpoint: 'https://api.start.gg/gql/alpha',
265
- method: 'POST',
266
- authIn: 'header',
267
- headers: {
268
- 'Content-Type': 'application/json',
269
- Accept: 'application/json'
270
- },
271
- body: {
272
- query: `query { currentUser { id slug email player { gamerTag } } }`
273
- }
274
- },
275
- strava: {
276
- endpoint: 'https://www.strava.com/api/v3/athlete',
277
- method: 'GET',
278
- authIn: 'header'
279
- },
280
- synology: {
281
- endpoint: 'https://<YOUR_DOMAIN>/webman/sso/SSOUserInfo.cgi',
282
- method: 'GET',
283
- authIn: 'header'
284
- },
285
- tiktok: {
286
- endpoint: 'https://open.douyin.com/oauth/userinfo',
287
- method: 'GET',
288
- authIn: 'query',
289
- tokenParam: 'access_token'
290
- },
291
- tiltify: {
292
- endpoint: 'https://tiltify.com/api/v3/me',
293
- method: 'GET',
294
- authIn: 'header'
295
- },
296
- tumblr: {
297
- endpoint: 'https://api.tumblr.com/v2/user/info',
298
- method: 'GET',
299
- authIn: 'header'
300
- },
301
- twitch: {
302
- endpoint: 'https://api.twitch.tv/helix/users',
303
- method: 'GET',
304
- authIn: 'header'
305
- },
306
- twitter: {
307
- endpoint: 'https://api.twitter.com/2/users/me',
308
- method: 'GET',
309
- authIn: 'header'
310
- },
311
- vk: {
312
- endpoint: 'https://api.vk.com/method/users.get',
313
- method: 'GET',
314
- authIn: 'query',
315
- tokenParam: 'access_token'
316
- },
317
- zoom: {
318
- endpoint: 'https://api.zoom.us/v2/users/me',
319
- method: 'GET',
320
- authIn: 'header'
321
- }
322
- };
@@ -1,33 +0,0 @@
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 DELETED
@@ -1,109 +0,0 @@
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 DELETED
@@ -1,63 +0,0 @@
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 DELETED
@@ -1,61 +0,0 @@
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
- );