nuxt-auth-utils 0.5.27 → 0.5.28
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 +3 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +6 -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/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/package.json +1 -1
package/README.md
CHANGED
|
@@ -239,6 +239,7 @@ It can also be set using environment variables:
|
|
|
239
239
|
- OIDC / OpenID Connect (Generic)
|
|
240
240
|
- Okta
|
|
241
241
|
- Ory
|
|
242
|
+
- osu!
|
|
242
243
|
- PayPal
|
|
243
244
|
- Polar
|
|
244
245
|
- Salesforce
|
|
@@ -449,6 +450,8 @@ export default defineWebAuthnRegisterEventHandler({
|
|
|
449
450
|
},
|
|
450
451
|
})
|
|
451
452
|
```
|
|
453
|
+
> [!TIP]
|
|
454
|
+
> 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
455
|
|
|
453
456
|
```ts
|
|
454
457
|
// server/api/webauthn/authenticate.post.ts
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -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
|
+
}
|
|
@@ -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' | (string & {});
|
|
4
4
|
export type OnError = (event: H3Event, error: H3Error) => Promise<void> | void;
|
|
5
5
|
export interface OAuthConfig<TConfig, TResult = {
|
|
6
6
|
user: any;
|