nuxt-auth-utils 0.5.27 → 0.5.29
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 +6 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +18 -0
- package/dist/runtime/server/lib/oauth/box.d.ts +95 -0
- package/dist/runtime/server/lib/oauth/box.js +69 -0
- package/dist/runtime/server/lib/oauth/keycloak.js +2 -0
- package/dist/runtime/server/lib/oauth/kick.d.ts +18 -1
- package/dist/runtime/server/lib/oauth/oidc.d.ts +0 -1
- package/dist/runtime/server/lib/oauth/oidc.js +18 -9
- package/dist/runtime/server/lib/oauth/osu.d.ts +42 -0
- package/dist/runtime/server/lib/oauth/osu.js +63 -0
- package/dist/runtime/server/lib/oauth/riotgames.d.ts +74 -0
- package/dist/runtime/server/lib/oauth/riotgames.js +83 -0
- package/dist/runtime/server/lib/oauth/twitch.d.ts +25 -1
- package/dist/runtime/server/lib/utils.d.ts +3 -0
- package/dist/runtime/server/lib/utils.js +20 -0
- package/dist/runtime/types/oauth-config.d.ts +1 -1
- package/dist/runtime/types/webauthn.d.ts +1 -1
- package/package.json +14 -14
package/README.md
CHANGED
|
@@ -219,6 +219,7 @@ It can also be set using environment variables:
|
|
|
219
219
|
- Azure B2C
|
|
220
220
|
- Battle.net
|
|
221
221
|
- Bluesky (AT Protocol)
|
|
222
|
+
- Box.com
|
|
222
223
|
- Discord
|
|
223
224
|
- Dropbox
|
|
224
225
|
- Facebook
|
|
@@ -239,8 +240,10 @@ It can also be set using environment variables:
|
|
|
239
240
|
- OIDC / OpenID Connect (Generic)
|
|
240
241
|
- Okta
|
|
241
242
|
- Ory
|
|
243
|
+
- osu!
|
|
242
244
|
- PayPal
|
|
243
245
|
- Polar
|
|
246
|
+
- Riot Games
|
|
244
247
|
- Salesforce
|
|
245
248
|
- Seznam
|
|
246
249
|
- Shopify Customer
|
|
@@ -303,7 +306,7 @@ if (await verifyPassword(hashedPassword, 'user_password')) {
|
|
|
303
306
|
It also provides a `passwordNeedsRehash` function to check if a password needs to be rehashed. This is useful when the hash settings are changed, such as as increasing the scrypt cost parameters.
|
|
304
307
|
|
|
305
308
|
```ts
|
|
306
|
-
const needsRehash =
|
|
309
|
+
const needsRehash = passwordNeedsRehash(hashedPassword)
|
|
307
310
|
|
|
308
311
|
if (needsRehash) {
|
|
309
312
|
// Password needs to be rehashed
|
|
@@ -449,6 +452,8 @@ export default defineWebAuthnRegisterEventHandler({
|
|
|
449
452
|
},
|
|
450
453
|
})
|
|
451
454
|
```
|
|
455
|
+
> [!TIP]
|
|
456
|
+
> If you want to plug in and use the [Community sourced list of AAGUIDS](https://github.com/passkeydeveloper/passkey-authenticator-aaguids) to decorate your display of added passkeys, you can access the the ID via `credential.aaguid` inside `onSuccess`
|
|
452
457
|
|
|
453
458
|
```ts
|
|
454
459
|
// server/api/webauthn/authenticate.post.ts
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -115,6 +115,12 @@ const module$1 = defineNuxtModule({
|
|
|
115
115
|
redirectURL: "",
|
|
116
116
|
baseURL: ""
|
|
117
117
|
});
|
|
118
|
+
runtimeConfig.oauth.box = defu(runtimeConfig.oauth.box, {
|
|
119
|
+
clientId: "",
|
|
120
|
+
clientSecret: "",
|
|
121
|
+
redirectURL: "",
|
|
122
|
+
scope: []
|
|
123
|
+
});
|
|
118
124
|
runtimeConfig.oauth.github = defu(runtimeConfig.oauth.github, {
|
|
119
125
|
clientId: "",
|
|
120
126
|
clientSecret: "",
|
|
@@ -400,6 +406,18 @@ const module$1 = defineNuxtModule({
|
|
|
400
406
|
redirectURL: "",
|
|
401
407
|
scope: []
|
|
402
408
|
});
|
|
409
|
+
runtimeConfig.oauth.osu = defu(runtimeConfig.oauth.osu, {
|
|
410
|
+
clientId: "",
|
|
411
|
+
clientSecret: "",
|
|
412
|
+
redirectURL: "",
|
|
413
|
+
scope: []
|
|
414
|
+
});
|
|
415
|
+
runtimeConfig.oauth.riotgames = defu(runtimeConfig.oauth.riotgames, {
|
|
416
|
+
clientId: "",
|
|
417
|
+
clientSecret: "",
|
|
418
|
+
redirectURL: "",
|
|
419
|
+
scope: []
|
|
420
|
+
});
|
|
403
421
|
}
|
|
404
422
|
});
|
|
405
423
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { OAuthConfig } from '#auth-utils';
|
|
2
|
+
export interface OAuthBoxConfig {
|
|
3
|
+
/**
|
|
4
|
+
* Box OAuth Client ID
|
|
5
|
+
* @default process.env.NUXT_OAUTH_BOX_CLIENT_ID
|
|
6
|
+
*/
|
|
7
|
+
clientId?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Box OAuth Client Secret
|
|
10
|
+
* @default process.env.NUXT_OAUTH_BOX_CLIENT_SECRET
|
|
11
|
+
*/
|
|
12
|
+
clientSecret?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Box OAuth Scope
|
|
15
|
+
* @default []
|
|
16
|
+
* @see https://developer.box.com/guides/api-calls/permissions-and-errors/scopes/
|
|
17
|
+
*/
|
|
18
|
+
scope?: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Box OAuth Authorization URL
|
|
21
|
+
* @default 'https://account.box.com/api/oauth2/authorize'
|
|
22
|
+
*/
|
|
23
|
+
authorizationURL?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Box OAuth Token URL
|
|
26
|
+
* @default 'https://api.box.com/oauth2/token'
|
|
27
|
+
*/
|
|
28
|
+
tokenURL?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Box User Info URL
|
|
31
|
+
* @default 'https://api.box.com/2.0/users/me'
|
|
32
|
+
*/
|
|
33
|
+
userURL?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Extra authorization parameters to provide to the authorization URL
|
|
36
|
+
* @see https://developer.box.com/guides/authentication/oauth2/oauth2-setup/
|
|
37
|
+
*/
|
|
38
|
+
authorizationParams?: Record<string, string>;
|
|
39
|
+
/**
|
|
40
|
+
* Redirect URL to allow overriding for situations like prod failing to determine public hostname
|
|
41
|
+
* @default process.env.NUXT_OAUTH_BOX_REDIRECT_URL
|
|
42
|
+
*/
|
|
43
|
+
redirectURL?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Box User object returned from /2.0/users/me
|
|
47
|
+
* @see https://developer.box.com/reference/get-users-me/
|
|
48
|
+
* @see https://www.postman.com/boxdev/box-s-public-workspace/example/8119550-f7344611-7834-4040-a4ae-d6b3ef95bfdb
|
|
49
|
+
*/
|
|
50
|
+
interface OAuthBoxUser {
|
|
51
|
+
type: 'user';
|
|
52
|
+
id: string;
|
|
53
|
+
name: string;
|
|
54
|
+
login: string;
|
|
55
|
+
created_at: string;
|
|
56
|
+
modified_at: string;
|
|
57
|
+
language: string;
|
|
58
|
+
timezone: string;
|
|
59
|
+
space_amount: number;
|
|
60
|
+
space_used: number;
|
|
61
|
+
max_upload_size: number;
|
|
62
|
+
status: 'active' | 'inactive' | 'cannot_delete_edit' | 'cannot_delete_edit_upload';
|
|
63
|
+
job_title?: string;
|
|
64
|
+
phone?: string;
|
|
65
|
+
address?: string;
|
|
66
|
+
avatar_url?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Box OAuth tokens
|
|
70
|
+
* @see https://developer.box.com/reference/post-oauth2-token/
|
|
71
|
+
* @see https://www.postman.com/boxdev/box-s-public-workspace/example/8119550-70a8e5bd-4d25-494c-be2c-5409db9d1ace
|
|
72
|
+
*/
|
|
73
|
+
interface OAuthBoxTokens {
|
|
74
|
+
access_token: string;
|
|
75
|
+
refresh_token: string;
|
|
76
|
+
expires_in: number;
|
|
77
|
+
token_type: 'bearer';
|
|
78
|
+
restricted_to?: Array<{
|
|
79
|
+
scope: string;
|
|
80
|
+
object?: {
|
|
81
|
+
type: string;
|
|
82
|
+
id: string;
|
|
83
|
+
};
|
|
84
|
+
}>;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Define an OAuth event handler for Box authentication.
|
|
88
|
+
* @see https://developer.box.com/guides/authentication/oauth2/
|
|
89
|
+
* @see https://www.postman.com/boxdev/box-s-public-workspace/collection/trhp912/box-platform-api
|
|
90
|
+
*/
|
|
91
|
+
export declare function defineOAuthBoxEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthBoxConfig, {
|
|
92
|
+
user: OAuthBoxUser;
|
|
93
|
+
tokens: OAuthBoxTokens;
|
|
94
|
+
}>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
|
|
95
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { eventHandler, getQuery, sendRedirect, createError } from "h3";
|
|
2
|
+
import { withQuery } from "ufo";
|
|
3
|
+
import { defu } from "defu";
|
|
4
|
+
import { getOAuthRedirectURL, handleAccessTokenErrorResponse, handleInvalidState, handleMissingConfiguration, handleState, requestAccessToken } from "../utils.js";
|
|
5
|
+
import { useRuntimeConfig } from "#imports";
|
|
6
|
+
export function defineOAuthBoxEventHandler({ config, onSuccess, onError }) {
|
|
7
|
+
return eventHandler(async (event) => {
|
|
8
|
+
config = defu(config, useRuntimeConfig(event).oauth?.box, {
|
|
9
|
+
authorizationURL: "https://account.box.com/api/oauth2/authorize",
|
|
10
|
+
tokenURL: "https://api.box.com/oauth2/token",
|
|
11
|
+
userURL: "https://api.box.com/2.0/users/me",
|
|
12
|
+
authorizationParams: {}
|
|
13
|
+
});
|
|
14
|
+
const query = getQuery(event);
|
|
15
|
+
if (query.error) {
|
|
16
|
+
const errorMessageParts = [query.error, query.error_description].filter(Boolean).join(": ");
|
|
17
|
+
const error = createError({
|
|
18
|
+
statusCode: 401,
|
|
19
|
+
message: `Box login failed: ${errorMessageParts || "Unknown error"}`,
|
|
20
|
+
data: query
|
|
21
|
+
});
|
|
22
|
+
if (!onError) throw error;
|
|
23
|
+
return onError(event, error);
|
|
24
|
+
}
|
|
25
|
+
if (!config.clientId || !config.clientSecret) {
|
|
26
|
+
return handleMissingConfiguration(event, "box", ["clientId", "clientSecret"], onError);
|
|
27
|
+
}
|
|
28
|
+
const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
|
|
29
|
+
const state = await handleState(event);
|
|
30
|
+
if (!query.code) {
|
|
31
|
+
const scope = config.scope || [];
|
|
32
|
+
return sendRedirect(
|
|
33
|
+
event,
|
|
34
|
+
withQuery(config.authorizationURL, {
|
|
35
|
+
response_type: "code",
|
|
36
|
+
client_id: config.clientId,
|
|
37
|
+
redirect_uri: redirectURL,
|
|
38
|
+
scope: scope.join(" "),
|
|
39
|
+
state,
|
|
40
|
+
...config.authorizationParams
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
if (query.state !== state) {
|
|
45
|
+
return handleInvalidState(event, "box", onError);
|
|
46
|
+
}
|
|
47
|
+
const tokens = await requestAccessToken(config.tokenURL, {
|
|
48
|
+
body: {
|
|
49
|
+
grant_type: "authorization_code",
|
|
50
|
+
client_id: config.clientId,
|
|
51
|
+
client_secret: config.clientSecret,
|
|
52
|
+
redirect_uri: redirectURL,
|
|
53
|
+
code: query.code
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
if (tokens.error) {
|
|
57
|
+
return handleAccessTokenErrorResponse(event, "box", tokens, onError);
|
|
58
|
+
}
|
|
59
|
+
const user = await $fetch(config.userURL, {
|
|
60
|
+
headers: {
|
|
61
|
+
Authorization: `Bearer ${tokens.access_token}`
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
return onSuccess(event, {
|
|
65
|
+
user,
|
|
66
|
+
tokens
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
@@ -56,6 +56,8 @@ export function defineOAuthKeycloakEventHandler({
|
|
|
56
56
|
redirect_uri: redirectURL,
|
|
57
57
|
code: query.code
|
|
58
58
|
}
|
|
59
|
+
}).catch((error) => {
|
|
60
|
+
return handleAccessTokenErrorResponse(event, "keycloak", error?.data || error, onError);
|
|
59
61
|
});
|
|
60
62
|
if (tokens.error) {
|
|
61
63
|
return handleAccessTokenErrorResponse(event, "keycloak", tokens, onError);
|
|
@@ -35,4 +35,21 @@ export interface OAuthKickConfig {
|
|
|
35
35
|
*/
|
|
36
36
|
redirectURL?: string;
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
interface KickUser {
|
|
39
|
+
email: string;
|
|
40
|
+
name: string;
|
|
41
|
+
profile_picture: string;
|
|
42
|
+
user_id: string;
|
|
43
|
+
}
|
|
44
|
+
interface KickTokens {
|
|
45
|
+
access_token: string;
|
|
46
|
+
expires_in: number;
|
|
47
|
+
refresh_token: string;
|
|
48
|
+
scope: string;
|
|
49
|
+
token_type: string;
|
|
50
|
+
}
|
|
51
|
+
export declare function defineOAuthKickEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthKickConfig, {
|
|
52
|
+
user: KickUser;
|
|
53
|
+
tokens: KickTokens;
|
|
54
|
+
}>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
|
|
55
|
+
export {};
|
|
@@ -2,7 +2,7 @@ import { useRuntimeConfig } from "#imports";
|
|
|
2
2
|
import { defu } from "defu";
|
|
3
3
|
import { createError, eventHandler, getQuery, sendRedirect } from "h3";
|
|
4
4
|
import { withQuery } from "ufo";
|
|
5
|
-
import { getOAuthRedirectURL, handleAccessTokenErrorResponse, handleInvalidState, handleMissingConfiguration, handlePkceVerifier, handleState, requestAccessToken } from "../utils.js";
|
|
5
|
+
import { getOAuthRedirectURL, handleAccessTokenErrorResponse, handleInvalidState, handleMissingConfiguration, handlePkceVerifier, handleState, requestAccessToken, handleNonce, parseJwt } from "../utils.js";
|
|
6
6
|
export function defineOAuthOidcEventHandler({ config, onSuccess, onError }) {
|
|
7
7
|
return eventHandler(async (event) => {
|
|
8
8
|
config = defu(config, useRuntimeConfig(event).oauth.oidc, {
|
|
@@ -24,7 +24,8 @@ export function defineOAuthOidcEventHandler({ config, onSuccess, onError }) {
|
|
|
24
24
|
const oidcConfig = typeof config.openidConfig === "string" ? await $fetch(config.openidConfig) : config.openidConfig;
|
|
25
25
|
const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
|
|
26
26
|
const state = await handleState(event);
|
|
27
|
-
const verifier =
|
|
27
|
+
const verifier = await handlePkceVerifier(event);
|
|
28
|
+
const nonce = handleNonce(event);
|
|
28
29
|
if (!query.code) {
|
|
29
30
|
config.scope = config.scope || [];
|
|
30
31
|
const authQuery = {
|
|
@@ -32,13 +33,12 @@ export function defineOAuthOidcEventHandler({ config, onSuccess, onError }) {
|
|
|
32
33
|
redirect_uri: redirectURL,
|
|
33
34
|
scope: config.scope.join(" "),
|
|
34
35
|
state,
|
|
36
|
+
nonce,
|
|
35
37
|
response_type: "code",
|
|
36
38
|
...config.params?.authorization_endpoint
|
|
37
39
|
};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
authQuery.code_challenge_method = verifier.code_challenge_method;
|
|
41
|
-
}
|
|
40
|
+
authQuery.code_challenge = verifier.code_challenge;
|
|
41
|
+
authQuery.code_challenge_method = verifier.code_challenge_method;
|
|
42
42
|
return sendRedirect(
|
|
43
43
|
event,
|
|
44
44
|
withQuery(oidcConfig.authorization_endpoint, authQuery)
|
|
@@ -53,17 +53,26 @@ export function defineOAuthOidcEventHandler({ config, onSuccess, onError }) {
|
|
|
53
53
|
client_secret: config.clientSecret,
|
|
54
54
|
redirect_uri: redirectURL,
|
|
55
55
|
code: query.code,
|
|
56
|
+
code_verifier: verifier.code_verifier,
|
|
56
57
|
...config.params?.token_endpoint
|
|
57
58
|
};
|
|
58
|
-
if (verifier) {
|
|
59
|
-
tokenQuery.code_verifier = verifier.code_verifier;
|
|
60
|
-
}
|
|
61
59
|
const tokens = await requestAccessToken(oidcConfig.token_endpoint, {
|
|
62
60
|
body: tokenQuery
|
|
63
61
|
});
|
|
64
62
|
if (tokens.error) {
|
|
65
63
|
return handleAccessTokenErrorResponse(event, "oidc", tokens, onError);
|
|
66
64
|
}
|
|
65
|
+
if (tokens.id_token) {
|
|
66
|
+
const claims = parseJwt(tokens.id_token);
|
|
67
|
+
if (claims.nonce !== nonce) {
|
|
68
|
+
const error = createError({
|
|
69
|
+
statusCode: 401,
|
|
70
|
+
message: "OIDC login failed: nonce mismatch"
|
|
71
|
+
});
|
|
72
|
+
if (!onError) throw error;
|
|
73
|
+
return onError(event, error);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
67
76
|
let user = {};
|
|
68
77
|
if (oidcConfig.userinfo_endpoint) {
|
|
69
78
|
user = await $fetch(oidcConfig.userinfo_endpoint, {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { OAuthConfig } from '#auth-utils';
|
|
2
|
+
export interface OAuthOsuConfig {
|
|
3
|
+
/**
|
|
4
|
+
* osu! OAuth Client ID
|
|
5
|
+
* @default process.env.NUXT_OAUTH_OSU_CLIENT_ID
|
|
6
|
+
*/
|
|
7
|
+
clientId?: string;
|
|
8
|
+
/**
|
|
9
|
+
* osu! OAuth Client Secret
|
|
10
|
+
* @default process.env.NUXT_OAUTH_OSU_CLIENT_SECRET
|
|
11
|
+
*/
|
|
12
|
+
clientSecret?: string;
|
|
13
|
+
/**
|
|
14
|
+
* osu! OAuth Scope
|
|
15
|
+
*
|
|
16
|
+
* The identify scope is always implicitly provided.
|
|
17
|
+
* @default []
|
|
18
|
+
* @see https://osu.ppy.sh/docs/#scopes
|
|
19
|
+
*/
|
|
20
|
+
scope?: string[];
|
|
21
|
+
/**
|
|
22
|
+
* osu! OAuth Authorization URL
|
|
23
|
+
* @default 'https://osu.ppy.sh/oauth/authorize'
|
|
24
|
+
*/
|
|
25
|
+
authorizationURL?: string;
|
|
26
|
+
/**
|
|
27
|
+
* osu! OAuth Token URL
|
|
28
|
+
* @default 'https://osu.ppy.sh/oauth/token'
|
|
29
|
+
*/
|
|
30
|
+
tokenURL?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Extra authorization parameters to provide to the authorization URL
|
|
33
|
+
* @see 'https://osu.ppy.sh/docs/#authorization-code-grant'
|
|
34
|
+
*/
|
|
35
|
+
authorizationParams?: Record<string, string>;
|
|
36
|
+
/**
|
|
37
|
+
* Redirect URL to to allow overriding for situations like prod failing to determine public hostname
|
|
38
|
+
* @default process.env.NUXT_OAUTH_OSU_REDIRECT_URL or current URL
|
|
39
|
+
*/
|
|
40
|
+
redirectURL?: string;
|
|
41
|
+
}
|
|
42
|
+
export declare function defineOAuthOsuEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthOsuConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { eventHandler, getQuery, sendRedirect } from "h3";
|
|
2
|
+
import { withQuery } from "ufo";
|
|
3
|
+
import { defu } from "defu";
|
|
4
|
+
import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken, handleState, handleInvalidState } from "../utils.js";
|
|
5
|
+
import { useRuntimeConfig } from "#imports";
|
|
6
|
+
export function defineOAuthOsuEventHandler({ config, onSuccess, onError }) {
|
|
7
|
+
return eventHandler(async (event) => {
|
|
8
|
+
config = defu(config, useRuntimeConfig(event).oauth?.osu, {
|
|
9
|
+
authorizationURL: "https://osu.ppy.sh/oauth/authorize",
|
|
10
|
+
tokenURL: "https://osu.ppy.sh/oauth/token",
|
|
11
|
+
authorizationParams: {}
|
|
12
|
+
});
|
|
13
|
+
const query = getQuery(event);
|
|
14
|
+
if (!config.clientId || !config.clientSecret) {
|
|
15
|
+
return handleMissingConfiguration(event, "osu", ["clientId", "clientSecret"], onError);
|
|
16
|
+
}
|
|
17
|
+
if (query.error) {
|
|
18
|
+
return handleAccessTokenErrorResponse(event, "osu", query, onError);
|
|
19
|
+
}
|
|
20
|
+
const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
|
|
21
|
+
const state = await handleState(event);
|
|
22
|
+
if (!query.code) {
|
|
23
|
+
config.scope = config.scope || [];
|
|
24
|
+
return sendRedirect(
|
|
25
|
+
event,
|
|
26
|
+
withQuery(config.authorizationURL, {
|
|
27
|
+
response_type: "code",
|
|
28
|
+
client_id: config.clientId,
|
|
29
|
+
redirect_uri: redirectURL,
|
|
30
|
+
scope: config.scope.join(" "),
|
|
31
|
+
state,
|
|
32
|
+
...config.authorizationParams
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
if (query.state !== state) {
|
|
37
|
+
return handleInvalidState(event, "osu", onError);
|
|
38
|
+
}
|
|
39
|
+
const tokens = await requestAccessToken(config.tokenURL, {
|
|
40
|
+
body: {
|
|
41
|
+
client_id: config.clientId,
|
|
42
|
+
client_secret: config.clientSecret,
|
|
43
|
+
grant_type: "authorization_code",
|
|
44
|
+
redirect_uri: redirectURL,
|
|
45
|
+
code: query.code
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
if (tokens.error) {
|
|
49
|
+
return handleAccessTokenErrorResponse(event, "osu", tokens, onError);
|
|
50
|
+
}
|
|
51
|
+
const accessToken = tokens.access_token;
|
|
52
|
+
const user = await $fetch("https://osu.ppy.sh/api/v2/me", {
|
|
53
|
+
headers: {
|
|
54
|
+
"user-agent": "Nuxt Auth Utils",
|
|
55
|
+
"Authorization": `Bearer ${accessToken}`
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
return onSuccess(event, {
|
|
59
|
+
tokens,
|
|
60
|
+
user
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { OAuthConfig } from '#auth-utils';
|
|
2
|
+
export interface OAuthRiotGamesConfig {
|
|
3
|
+
/**
|
|
4
|
+
* RiotGames OAuth Client ID
|
|
5
|
+
* @default process.env.NUXT_OAUTH_RIOTGAMES_CLIENT_ID
|
|
6
|
+
*/
|
|
7
|
+
clientId?: string;
|
|
8
|
+
/**
|
|
9
|
+
* RiotGames OAuth Client Secret
|
|
10
|
+
* @default process.env.NUXT_OAUTH_RIOTGAMES_CLIENT_SECRET
|
|
11
|
+
*/
|
|
12
|
+
clientSecret?: string;
|
|
13
|
+
/**
|
|
14
|
+
* RiotGames OAuth Scope
|
|
15
|
+
* @default []
|
|
16
|
+
* @see https://beta.developer.riotgames.com/sign-on
|
|
17
|
+
* @example ['cpid', 'offline_access']
|
|
18
|
+
*/
|
|
19
|
+
scope?: string[];
|
|
20
|
+
/**
|
|
21
|
+
* RiotGames OAuth Authorization URL
|
|
22
|
+
* @default 'https://auth.riotgames.com/authorize'
|
|
23
|
+
*/
|
|
24
|
+
authorizationURL?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Riot OAuth Token URL
|
|
27
|
+
* @default 'https://auth.riotgames.com/token'
|
|
28
|
+
*/
|
|
29
|
+
tokenURL?: string;
|
|
30
|
+
/**
|
|
31
|
+
* RiotGames API URL
|
|
32
|
+
* @default 'https://auth.riotgames.com'
|
|
33
|
+
*/
|
|
34
|
+
apiURL?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Extra authorization parameters to provide to the authorization URL
|
|
37
|
+
*/
|
|
38
|
+
authorizationParams?: Record<string, string>;
|
|
39
|
+
/**
|
|
40
|
+
* Redirect URL to to allow overriding for situations like prod failing to determine public hostname
|
|
41
|
+
* @default process.env.NUXT_OAUTH_RIOTGAMES_REDIRECT_URL
|
|
42
|
+
*/
|
|
43
|
+
redirectURL?: string;
|
|
44
|
+
/**
|
|
45
|
+
* API routing values for Accounts Endpoint. You can query for any account in any region.
|
|
46
|
+
* @default 'europe'
|
|
47
|
+
*/
|
|
48
|
+
region?: 'americas' | 'europe' | 'asia';
|
|
49
|
+
}
|
|
50
|
+
interface RiotGamesUser extends RiotGamesAccount, RiotGamesUserInfo {
|
|
51
|
+
}
|
|
52
|
+
interface RiotGamesAccount {
|
|
53
|
+
puuid: string;
|
|
54
|
+
gameName: string;
|
|
55
|
+
tagLine: string;
|
|
56
|
+
}
|
|
57
|
+
interface RiotGamesUserInfo {
|
|
58
|
+
sub: string;
|
|
59
|
+
cpid?: string;
|
|
60
|
+
jti: string;
|
|
61
|
+
}
|
|
62
|
+
interface RiotGamesTokens {
|
|
63
|
+
access_token: string;
|
|
64
|
+
refresh_token: string;
|
|
65
|
+
id_token: string;
|
|
66
|
+
expires_in: number;
|
|
67
|
+
scope: string;
|
|
68
|
+
token_type: string;
|
|
69
|
+
}
|
|
70
|
+
export declare function defineOAuthRiotGamesEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthRiotGamesConfig, {
|
|
71
|
+
user: RiotGamesUser;
|
|
72
|
+
tokens: RiotGamesTokens;
|
|
73
|
+
}>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
|
|
74
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { createError, eventHandler, getQuery, sendRedirect } from "h3";
|
|
2
|
+
import { withQuery } from "ufo";
|
|
3
|
+
import { defu } from "defu";
|
|
4
|
+
import { getOAuthRedirectURL, handleAccessTokenErrorResponse, handleInvalidState, handleMissingConfiguration, handleState, requestAccessToken } from "../utils.js";
|
|
5
|
+
import { useRuntimeConfig } from "#imports";
|
|
6
|
+
export function defineOAuthRiotGamesEventHandler({ config, onSuccess, onError }) {
|
|
7
|
+
return eventHandler(async (event) => {
|
|
8
|
+
config = defu(config, useRuntimeConfig(event).oauth?.riotgames, {
|
|
9
|
+
authorizationURL: "https://auth.riotgames.com/authorize",
|
|
10
|
+
tokenURL: "https://auth.riotgames.com/token",
|
|
11
|
+
apiURL: "https://auth.riotgames.com",
|
|
12
|
+
region: "europe"
|
|
13
|
+
});
|
|
14
|
+
const query = getQuery(event);
|
|
15
|
+
if (query.error) {
|
|
16
|
+
const error = createError({
|
|
17
|
+
statusCode: 401,
|
|
18
|
+
message: `RiotGames login failed: ${query.error || "Unknown error"}`,
|
|
19
|
+
data: query
|
|
20
|
+
});
|
|
21
|
+
if (!onError) throw error;
|
|
22
|
+
return onError(event, error);
|
|
23
|
+
}
|
|
24
|
+
if (!config.clientId || !config.clientSecret) {
|
|
25
|
+
return handleMissingConfiguration(event, "riotgames", ["clientId", "clientSecret"], onError);
|
|
26
|
+
}
|
|
27
|
+
const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
|
|
28
|
+
const state = await handleState(event);
|
|
29
|
+
if (!query.code) {
|
|
30
|
+
config.scope = config.scope || [];
|
|
31
|
+
if (!config.scope.includes("openid")) {
|
|
32
|
+
config.scope.push("openid");
|
|
33
|
+
}
|
|
34
|
+
return sendRedirect(
|
|
35
|
+
event,
|
|
36
|
+
withQuery(config.authorizationURL, {
|
|
37
|
+
client_id: config.clientId,
|
|
38
|
+
redirect_uri: redirectURL,
|
|
39
|
+
response_type: "code",
|
|
40
|
+
scope: config.scope.join(" "),
|
|
41
|
+
state,
|
|
42
|
+
...config.authorizationParams
|
|
43
|
+
})
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (query.state !== state) {
|
|
47
|
+
return handleInvalidState(event, "riotgames", onError);
|
|
48
|
+
}
|
|
49
|
+
const tokens = await requestAccessToken(config.tokenURL, {
|
|
50
|
+
body: {
|
|
51
|
+
grant_type: "authorization_code",
|
|
52
|
+
client_id: config.clientId,
|
|
53
|
+
client_secret: config.clientSecret,
|
|
54
|
+
redirect_uri: redirectURL,
|
|
55
|
+
code: query.code
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
if (tokens.error) {
|
|
59
|
+
return handleAccessTokenErrorResponse(event, "riotgames", tokens, onError);
|
|
60
|
+
}
|
|
61
|
+
const accessToken = tokens.access_token;
|
|
62
|
+
const [account, userInfo] = await Promise.all([
|
|
63
|
+
$fetch(`https://${config.region}.api.riotgames.com/riot/account/v1/accounts/me`, {
|
|
64
|
+
headers: {
|
|
65
|
+
Authorization: `Bearer ${accessToken}`
|
|
66
|
+
}
|
|
67
|
+
}),
|
|
68
|
+
$fetch(`${config.apiURL}/userinfo`, {
|
|
69
|
+
headers: {
|
|
70
|
+
Authorization: `Bearer ${accessToken}`
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
]);
|
|
74
|
+
const user = {
|
|
75
|
+
...account,
|
|
76
|
+
...userInfo
|
|
77
|
+
};
|
|
78
|
+
return onSuccess(event, {
|
|
79
|
+
user,
|
|
80
|
+
tokens
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
@@ -44,4 +44,28 @@ export interface OAuthTwitchConfig {
|
|
|
44
44
|
*/
|
|
45
45
|
redirectURL?: string;
|
|
46
46
|
}
|
|
47
|
-
|
|
47
|
+
interface TwitchUser {
|
|
48
|
+
id: string;
|
|
49
|
+
login: string;
|
|
50
|
+
display_name: string;
|
|
51
|
+
type: string;
|
|
52
|
+
broadcaster_type: string;
|
|
53
|
+
description: string;
|
|
54
|
+
profile_image_url: string;
|
|
55
|
+
offline_image_url: string;
|
|
56
|
+
view_count: number;
|
|
57
|
+
email?: string;
|
|
58
|
+
created_at: string;
|
|
59
|
+
}
|
|
60
|
+
interface TwitchTokens {
|
|
61
|
+
access_token: string;
|
|
62
|
+
expires_in: number;
|
|
63
|
+
refresh_token: string;
|
|
64
|
+
scope: string[];
|
|
65
|
+
token_type: string;
|
|
66
|
+
}
|
|
67
|
+
export declare function defineOAuthTwitchEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthTwitchConfig, {
|
|
68
|
+
user: TwitchUser;
|
|
69
|
+
tokens: TwitchTokens;
|
|
70
|
+
}>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
|
|
71
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type H3Event } from 'h3';
|
|
2
|
+
import * as jose from 'jose';
|
|
2
3
|
import type { OAuthProvider, OnError } from '#auth-utils';
|
|
3
4
|
export declare function getOAuthRedirectURL(event: H3Event): string;
|
|
4
5
|
/**
|
|
@@ -70,4 +71,6 @@ export declare function handlePkceVerifier(event: H3Event): Promise<{
|
|
|
70
71
|
code_challenge_method?: undefined;
|
|
71
72
|
}>;
|
|
72
73
|
export declare function handleState(event: H3Event): Promise<string | undefined>;
|
|
74
|
+
export declare function parseJwt(token: string): jose.JWTPayload;
|
|
75
|
+
export declare function handleNonce(event: H3Event): string | undefined;
|
|
73
76
|
export {};
|
|
@@ -121,3 +121,23 @@ export async function handleState(event) {
|
|
|
121
121
|
});
|
|
122
122
|
return state;
|
|
123
123
|
}
|
|
124
|
+
export function parseJwt(token) {
|
|
125
|
+
return jose.decodeJwt(token);
|
|
126
|
+
}
|
|
127
|
+
export function handleNonce(event) {
|
|
128
|
+
const query = getQuery(event);
|
|
129
|
+
if (query.code) {
|
|
130
|
+
const nonce2 = getCookie(event, "nuxt-auth-nonce");
|
|
131
|
+
deleteCookie(event, "nuxt-auth-nonce");
|
|
132
|
+
return nonce2;
|
|
133
|
+
}
|
|
134
|
+
const nonce = encodeBase64Url(getRandomBytes(8));
|
|
135
|
+
setCookie(event, "nuxt-auth-nonce", nonce, {
|
|
136
|
+
httpOnly: true,
|
|
137
|
+
secure: !isDevelopment,
|
|
138
|
+
sameSite: "lax",
|
|
139
|
+
maxAge: OAUTH_COOKIE_MAX_AGE,
|
|
140
|
+
path: "/"
|
|
141
|
+
});
|
|
142
|
+
return nonce;
|
|
143
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { H3Event, H3Error } from 'h3';
|
|
2
2
|
export type ATProtoProvider = 'bluesky';
|
|
3
|
-
export type OAuthProvider = ATProtoProvider | 'atlassian' | 'auth0' | 'authentik' | 'azureb2c' | 'battledotnet' | 'cognito' | 'discord' | 'dropbox' | 'facebook' | 'gitea' | 'github' | 'gitlab' | 'google' | 'hubspot' | 'instagram' | 'kick' | 'keycloak' | 'line' | 'linear' | 'linkedin' | 'microsoft' | 'paypal' | 'polar' | 'spotify' | 'seznam' | 'steam' | 'strava' | 'tiktok' | 'twitch' | 'vk' | 'workos' | 'x' | 'xsuaa' | 'yandex' | 'zitadel' | 'apple' | 'livechat' | 'salesforce' | 'slack' | 'heroku' | 'roblox' | 'okta' | 'ory' | 'shopifyCustomer' | '
|
|
3
|
+
export type OAuthProvider = ATProtoProvider | 'atlassian' | 'auth0' | 'authentik' | 'azureb2c' | 'battledotnet' | 'cognito' | 'discord' | 'dropbox' | 'facebook' | 'gitea' | 'github' | 'gitlab' | 'google' | 'hubspot' | 'instagram' | 'kick' | 'keycloak' | 'line' | 'linear' | 'linkedin' | 'microsoft' | 'paypal' | 'polar' | 'spotify' | 'seznam' | 'steam' | 'strava' | 'tiktok' | 'twitch' | 'vk' | 'workos' | 'x' | 'xsuaa' | 'yandex' | 'zitadel' | 'apple' | 'livechat' | 'salesforce' | 'slack' | 'heroku' | 'roblox' | 'okta' | 'ory' | 'shopifyCustomer' | 'oidc' | 'osu' | 'riotgames' | 'box' | (string & {});
|
|
4
4
|
export type OnError = (event: H3Event, error: H3Error) => Promise<void> | void;
|
|
5
5
|
export interface OAuthConfig<TConfig, TResult = {
|
|
6
6
|
user: any;
|
|
@@ -86,7 +86,7 @@ export interface WebAuthnComposable {
|
|
|
86
86
|
* @param user.displayName - The display name to register, used for convenience and personalization and should not be relied upon as a secure identifier for authentication processes
|
|
87
87
|
* @returns true if the registration was successful
|
|
88
88
|
*/
|
|
89
|
-
register: <T extends WebAuthnUser>(
|
|
89
|
+
register: <T extends WebAuthnUser>(user: T) => Promise<boolean>;
|
|
90
90
|
/**
|
|
91
91
|
* Authenticate a credential
|
|
92
92
|
* @param userName - The user name to authenticate, can be an email address, a username, or any other form of unique ID chosen by the user
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-auth-utils",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.29",
|
|
4
4
|
"description": "Add Authentication to Nuxt applications with secured & sealed cookies sessions.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"type": "module",
|
|
11
|
-
"packageManager": "pnpm@10.
|
|
11
|
+
"packageManager": "pnpm@10.29.3",
|
|
12
12
|
"exports": {
|
|
13
13
|
".": {
|
|
14
14
|
"types": "./dist/types.d.mts",
|
|
@@ -40,13 +40,13 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@adonisjs/hash": "^9.1.1",
|
|
43
|
-
"@nuxt/kit": "^4.
|
|
43
|
+
"@nuxt/kit": "^4.3.1",
|
|
44
44
|
"defu": "^6.1.4",
|
|
45
45
|
"h3": "^1.15.4",
|
|
46
46
|
"hookable": "^6.0.1",
|
|
47
47
|
"jose": "^6.1.3",
|
|
48
48
|
"ofetch": "^1.5.1",
|
|
49
|
-
"openid-client": "^6.8.
|
|
49
|
+
"openid-client": "^6.8.2",
|
|
50
50
|
"pathe": "^2.0.3",
|
|
51
51
|
"scule": "^1.3.0",
|
|
52
52
|
"uncrypto": "^0.1.3"
|
|
@@ -72,20 +72,20 @@
|
|
|
72
72
|
}
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
|
-
"@iconify-json/simple-icons": "^1.2.
|
|
76
|
-
"@nuxt/devtools": "3.
|
|
77
|
-
"@nuxt/eslint-config": "^1.
|
|
75
|
+
"@iconify-json/simple-icons": "^1.2.71",
|
|
76
|
+
"@nuxt/devtools": "3.2.1",
|
|
77
|
+
"@nuxt/eslint-config": "^1.15.1",
|
|
78
78
|
"@nuxt/module-builder": "^1.0.2",
|
|
79
|
-
"@nuxt/schema": "^4.
|
|
80
|
-
"@nuxt/test-utils": "^
|
|
79
|
+
"@nuxt/schema": "^4.3.1",
|
|
80
|
+
"@nuxt/test-utils": "^4.0.0",
|
|
81
81
|
"@types/node": "latest",
|
|
82
|
-
"@nuxt/ui": "^4.
|
|
82
|
+
"@nuxt/ui": "^4.4.0",
|
|
83
83
|
"@simplewebauthn/types": "^12.0.0",
|
|
84
84
|
"changelogen": "^0.6.2",
|
|
85
|
-
"eslint": "^
|
|
86
|
-
"nuxt": "^4.
|
|
85
|
+
"eslint": "^10.0.0",
|
|
86
|
+
"nuxt": "^4.3.1",
|
|
87
87
|
"typescript": "5.9.3",
|
|
88
|
-
"vitest": "^4.0.
|
|
89
|
-
"vue-tsc": "^3.2.
|
|
88
|
+
"vitest": "^4.0.18",
|
|
89
|
+
"vue-tsc": "^3.2.4"
|
|
90
90
|
}
|
|
91
91
|
}
|