nuxt-auth-utils 0.5.4 → 0.5.6
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 +19 -4
- package/dist/module.json +1 -1
- package/dist/module.mjs +17 -0
- package/dist/runtime/server/lib/oauth/instagram.d.ts +6 -6
- package/dist/runtime/server/lib/oauth/instagram.js +10 -6
- package/dist/runtime/server/lib/oauth/seznam.d.ts +95 -0
- package/dist/runtime/server/lib/oauth/seznam.js +57 -0
- package/dist/runtime/server/lib/oauth/strava.d.ts +116 -0
- package/dist/runtime/server/lib/oauth/strava.js +73 -0
- package/dist/runtime/server/lib/oauth/workos.d.ts +63 -0
- package/dist/runtime/server/lib/oauth/workos.js +53 -0
- package/dist/runtime/server/lib/oauth/xsuaa.js +1 -1
- package/dist/runtime/server/lib/oauth/zitadel.js +0 -1
- package/dist/runtime/server/lib/utils.d.ts +1 -0
- package/dist/runtime/server/lib/webauthn/register.d.ts +3 -2
- package/dist/runtime/server/lib/webauthn/register.js +7 -7
- package/dist/runtime/types/oauth-config.d.ts +1 -1
- package/dist/runtime/types/webauthn.d.ts +3 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -221,14 +221,18 @@ It can also be set using environment variables:
|
|
|
221
221
|
- Microsoft
|
|
222
222
|
- PayPal
|
|
223
223
|
- Polar
|
|
224
|
+
- Seznam
|
|
224
225
|
- Spotify
|
|
225
226
|
- Steam
|
|
227
|
+
- Strava
|
|
226
228
|
- TikTok
|
|
227
229
|
- Twitch
|
|
228
230
|
- VK
|
|
231
|
+
- WorkOS
|
|
229
232
|
- X (Twitter)
|
|
230
233
|
- XSUAA
|
|
231
234
|
- Yandex
|
|
235
|
+
- Zitadel
|
|
232
236
|
|
|
233
237
|
You can add your favorite provider by creating a new file in [src/runtime/server/lib/oauth/](./src/runtime/server/lib/oauth/).
|
|
234
238
|
|
|
@@ -349,10 +353,21 @@ The following code does not include the actual database queries, but shows the g
|
|
|
349
353
|
import { z } from 'zod'
|
|
350
354
|
export default defineWebAuthnRegisterEventHandler({
|
|
351
355
|
// optional
|
|
352
|
-
validateUser
|
|
353
|
-
//
|
|
354
|
-
|
|
355
|
-
|
|
356
|
+
async validateUser(userBody, event) {
|
|
357
|
+
// bonus: check if the user is already authenticated to link a credential to his account
|
|
358
|
+
// We first check if the user is already authenticated by getting the session
|
|
359
|
+
// And verify that the email is the same as the one in session
|
|
360
|
+
const session = await getUserSession(event)
|
|
361
|
+
if (session.user?.email && session.user.email !== body.userName) {
|
|
362
|
+
throw createError({ statusCode: 400, message: 'Email not matching curent session' })
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// If he registers a new account with credentials
|
|
366
|
+
return z.object({
|
|
367
|
+
// we want the userName to be a valid email
|
|
368
|
+
userName: z.string().email()
|
|
369
|
+
}).parse(userBody)
|
|
370
|
+
},
|
|
356
371
|
async onSuccess(event, { credential, user }) {
|
|
357
372
|
// The credential creation has been successful
|
|
358
373
|
// We need to create a user if it does not exist
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -126,6 +126,13 @@ const module = defineNuxtModule({
|
|
|
126
126
|
audience: "",
|
|
127
127
|
redirectURL: ""
|
|
128
128
|
});
|
|
129
|
+
runtimeConfig.oauth.workos = defu(runtimeConfig.oauth.workos, {
|
|
130
|
+
clientId: "",
|
|
131
|
+
clientSecret: "",
|
|
132
|
+
connectionId: "",
|
|
133
|
+
screenHint: "",
|
|
134
|
+
redirectURL: ""
|
|
135
|
+
});
|
|
129
136
|
runtimeConfig.oauth.microsoft = defu(runtimeConfig.oauth.microsoft, {
|
|
130
137
|
clientId: "",
|
|
131
138
|
clientSecret: "",
|
|
@@ -237,6 +244,16 @@ const module = defineNuxtModule({
|
|
|
237
244
|
domain: "",
|
|
238
245
|
redirectURL: ""
|
|
239
246
|
});
|
|
247
|
+
runtimeConfig.oauth.seznam = defu(runtimeConfig.oauth.seznam, {
|
|
248
|
+
clientId: "",
|
|
249
|
+
clientSecret: "",
|
|
250
|
+
redirectURL: ""
|
|
251
|
+
});
|
|
252
|
+
runtimeConfig.oauth.strava = defu(runtimeConfig.oauth.strava, {
|
|
253
|
+
clientId: "",
|
|
254
|
+
clientSecret: "",
|
|
255
|
+
redirectURL: ""
|
|
256
|
+
});
|
|
240
257
|
}
|
|
241
258
|
});
|
|
242
259
|
|
|
@@ -12,21 +12,21 @@ export interface OAuthInstagramConfig {
|
|
|
12
12
|
clientSecret?: string;
|
|
13
13
|
/**
|
|
14
14
|
* Instagram OAuth Scope
|
|
15
|
-
* @
|
|
15
|
+
* @required [ 'business_basic' ]
|
|
16
16
|
* @see https://developers.facebook.com/docs/instagram-basic-display-api/overview#permissions
|
|
17
|
-
* @example [ '
|
|
17
|
+
* @example [ 'business_basic', 'business_manage_messages' ],
|
|
18
18
|
*/
|
|
19
|
-
scope?:
|
|
19
|
+
scope?: ('business_basic' | 'business_content_publish' | 'business_manage_comments' | 'business_manage_messages')[];
|
|
20
20
|
/**
|
|
21
21
|
* Instagram OAuth User Fields
|
|
22
22
|
* @default [ 'id', 'username'],
|
|
23
23
|
* @see https://developers.facebook.com/docs/instagram-basic-display-api/reference/user#fields
|
|
24
|
-
* @example [ 'id', 'username', 'account_type', '
|
|
24
|
+
* @example [ 'id', 'username', 'user_id', 'account_type', 'profile_picture_url' ],
|
|
25
25
|
*/
|
|
26
|
-
fields?:
|
|
26
|
+
fields?: ('id' | 'user_id' | 'username' | 'name' | 'account_type' | 'profile_picture_url' | 'followers_count' | 'follows_count' | 'media_count')[];
|
|
27
27
|
/**
|
|
28
28
|
* Instagram OAuth Authorization URL
|
|
29
|
-
* @default 'https://
|
|
29
|
+
* @default 'https://www.instagram.com/oauth/authorize'
|
|
30
30
|
*/
|
|
31
31
|
authorizationURL?: string;
|
|
32
32
|
/**
|
|
@@ -10,8 +10,8 @@ export function defineOAuthInstagramEventHandler({
|
|
|
10
10
|
}) {
|
|
11
11
|
return eventHandler(async (event) => {
|
|
12
12
|
config = defu(config, useRuntimeConfig(event).oauth?.instagram, {
|
|
13
|
-
scope: ["
|
|
14
|
-
authorizationURL: "https://
|
|
13
|
+
scope: ["business_basic"],
|
|
14
|
+
authorizationURL: "https://www.instagram.com/oauth/authorize",
|
|
15
15
|
tokenURL: "https://api.instagram.com/oauth/access_token",
|
|
16
16
|
authorizationParams: {}
|
|
17
17
|
});
|
|
@@ -20,7 +20,11 @@ export function defineOAuthInstagramEventHandler({
|
|
|
20
20
|
const error = createError({
|
|
21
21
|
statusCode: 401,
|
|
22
22
|
message: `Instagram login failed: ${query.error || "Unknown error"}`,
|
|
23
|
-
data:
|
|
23
|
+
data: {
|
|
24
|
+
error: query.error,
|
|
25
|
+
error_reason: query.error_reason,
|
|
26
|
+
error_description: query.error_description
|
|
27
|
+
}
|
|
24
28
|
});
|
|
25
29
|
if (!onError) throw error;
|
|
26
30
|
return onError(event, error);
|
|
@@ -36,7 +40,7 @@ export function defineOAuthInstagramEventHandler({
|
|
|
36
40
|
withQuery(config.authorizationURL, {
|
|
37
41
|
client_id: config.clientId,
|
|
38
42
|
redirect_uri: redirectURL,
|
|
39
|
-
scope: config.scope.join(
|
|
43
|
+
scope: config.scope.join(),
|
|
40
44
|
response_type: "code"
|
|
41
45
|
})
|
|
42
46
|
);
|
|
@@ -55,9 +59,9 @@ export function defineOAuthInstagramEventHandler({
|
|
|
55
59
|
}
|
|
56
60
|
const accessToken = tokens.access_token;
|
|
57
61
|
config.fields = config.fields || ["id", "username"];
|
|
58
|
-
const fields = config.fields.join(
|
|
62
|
+
const fields = config.fields.join();
|
|
59
63
|
const user = await $fetch(
|
|
60
|
-
`https://graph.instagram.com/
|
|
64
|
+
`https://graph.instagram.com/v21.0/me?fields=${fields}&access_token=${accessToken}`
|
|
61
65
|
);
|
|
62
66
|
if (!user) {
|
|
63
67
|
throw new Error("Instagram login failed: no user found");
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { OAuthConfig } from '#auth-utils';
|
|
2
|
+
export interface OAuthSeznamConfig {
|
|
3
|
+
/**
|
|
4
|
+
* Seznam OAuth Client ID
|
|
5
|
+
* @default process.env.NUXT_OAUTH_SEZNAM_CLIENT_ID
|
|
6
|
+
*/
|
|
7
|
+
clientId?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Seznam OAuth Client Secret
|
|
10
|
+
* @default process.env.NUXT_OAUTH_SEZNAM_CLIENT_SECRET
|
|
11
|
+
*/
|
|
12
|
+
clientSecret?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Seznam OAuth Scope
|
|
15
|
+
* @default ['identity']
|
|
16
|
+
* @see https://vyvojari.seznam.cz/oauth/scopes?lang=en
|
|
17
|
+
* @example ['identity', 'avatar']
|
|
18
|
+
*/
|
|
19
|
+
scope?: string[];
|
|
20
|
+
/**
|
|
21
|
+
* Redirect URL to to allow overriding for situations like prod failing to determine public hostname
|
|
22
|
+
* Redirect URL has to be set as well in the Seznam OAuth settings https://vyvojari.seznam.cz/oauth/admin in order for it to work
|
|
23
|
+
* @see https://vyvojari.seznam.cz/oauth/admin
|
|
24
|
+
* @default process.env.NUXT_OAUTH_SEZNAM_REDIRECT_URL
|
|
25
|
+
*/
|
|
26
|
+
redirectURL?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Seznam OAuth Authorization URL
|
|
29
|
+
* @default 'https://login.szn.cz/api/v1/oauth/auth'
|
|
30
|
+
*/
|
|
31
|
+
authorizationURL?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Seznam OAuth Token URL
|
|
34
|
+
* @default 'https://login.szn.cz/api/v1/oauth/token'
|
|
35
|
+
*/
|
|
36
|
+
tokenURL?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Seznam OAuth User URL
|
|
39
|
+
* @default 'https://login.szn.cz/api/v1/user'
|
|
40
|
+
*/
|
|
41
|
+
userURL?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface OAuthSeznamUser {
|
|
44
|
+
/**
|
|
45
|
+
* Unique persistent user account identifier
|
|
46
|
+
*/
|
|
47
|
+
oauth_user_id: string;
|
|
48
|
+
/**
|
|
49
|
+
* The user's e-mail address or null for users without an e-mail
|
|
50
|
+
*/
|
|
51
|
+
email: string | null;
|
|
52
|
+
/**
|
|
53
|
+
* Given name (when available)
|
|
54
|
+
*/
|
|
55
|
+
firstname: string;
|
|
56
|
+
/**
|
|
57
|
+
* Family name (when available)
|
|
58
|
+
*/
|
|
59
|
+
lastname: string;
|
|
60
|
+
/**
|
|
61
|
+
* User account identifier suitable for usage within the Seznam ad infrastructure
|
|
62
|
+
*/
|
|
63
|
+
advert_user_id: string;
|
|
64
|
+
/**
|
|
65
|
+
* (only when provided and validated by the user; null otherwise)
|
|
66
|
+
* Available only when you use the contact-phone scope
|
|
67
|
+
* @see https://vyvojari.seznam.cz/oauth/scopes?lang=en
|
|
68
|
+
*/
|
|
69
|
+
contact_phone?: string | null;
|
|
70
|
+
/**
|
|
71
|
+
* the image's URL
|
|
72
|
+
* Available only when you use the avatar scope
|
|
73
|
+
* @see https://vyvojari.seznam.cz/oauth/scopes?lang=en
|
|
74
|
+
*/
|
|
75
|
+
avatar_url?: string | null;
|
|
76
|
+
/**
|
|
77
|
+
* true/false value corresponding to the user's adult status
|
|
78
|
+
* Available only when you use the avatar scope
|
|
79
|
+
* @see https://vyvojari.seznam.cz/oauth/scopes?lang=en
|
|
80
|
+
*/
|
|
81
|
+
adulthood?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* date of birth in the ISO 8601 format (only when present; null otherwise)
|
|
84
|
+
* Available only when you use the birthday scope
|
|
85
|
+
* @see https://vyvojari.seznam.cz/oauth/scopes?lang=en
|
|
86
|
+
*/
|
|
87
|
+
birthday?: string | null;
|
|
88
|
+
/**
|
|
89
|
+
* one of the allowed strings "Male" / "Female" / "Other" (null if not set)
|
|
90
|
+
* Available only when you use the gender scope
|
|
91
|
+
* @see https://vyvojari.seznam.cz/oauth/scopes?lang=en
|
|
92
|
+
*/
|
|
93
|
+
gender?: string | null;
|
|
94
|
+
}
|
|
95
|
+
export declare function defineOAuthSeznamEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthSeznamConfig, OAuthSeznamUser>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import defu from "defu";
|
|
2
|
+
import { eventHandler, getQuery, sendRedirect } from "h3";
|
|
3
|
+
import { withQuery } from "ufo";
|
|
4
|
+
import { getOAuthRedirectURL, handleAccessTokenErrorResponse, handleMissingConfiguration, requestAccessToken } from "../utils.js";
|
|
5
|
+
import { useRuntimeConfig } from "#imports";
|
|
6
|
+
export function defineOAuthSeznamEventHandler({ config, onSuccess, onError }) {
|
|
7
|
+
return eventHandler(async (event) => {
|
|
8
|
+
config = defu(config, useRuntimeConfig(event).oauth?.seznam, {
|
|
9
|
+
authorizationURL: "https://login.szn.cz/api/v1/oauth/auth",
|
|
10
|
+
tokenURL: "https://login.szn.cz/api/v1/oauth/token",
|
|
11
|
+
userURL: "https://login.szn.cz/api/v1/user"
|
|
12
|
+
});
|
|
13
|
+
const query = getQuery(event);
|
|
14
|
+
if (!config.clientId) {
|
|
15
|
+
return handleMissingConfiguration(event, "seznam", ["clientId"], onError);
|
|
16
|
+
}
|
|
17
|
+
const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
|
|
18
|
+
if (!query.code) {
|
|
19
|
+
config.scope = config.scope || ["identity"];
|
|
20
|
+
return sendRedirect(
|
|
21
|
+
event,
|
|
22
|
+
withQuery(config.authorizationURL, {
|
|
23
|
+
response_type: "code",
|
|
24
|
+
client_id: config.clientId,
|
|
25
|
+
redirect_uri: redirectURL,
|
|
26
|
+
scope: config.scope.join(","),
|
|
27
|
+
state: query.state || ""
|
|
28
|
+
})
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
const tokens = await requestAccessToken(config.tokenURL, {
|
|
32
|
+
body: {
|
|
33
|
+
grant_type: "authorization_code",
|
|
34
|
+
code: query.code,
|
|
35
|
+
client_id: config.clientId,
|
|
36
|
+
client_secret: config.clientSecret,
|
|
37
|
+
redirect_uri: redirectURL
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
if (tokens.error) {
|
|
41
|
+
return handleAccessTokenErrorResponse(event, "seznam", tokens, onError);
|
|
42
|
+
}
|
|
43
|
+
const accessToken = tokens.access_token;
|
|
44
|
+
const user = await $fetch(
|
|
45
|
+
config.userURL,
|
|
46
|
+
{
|
|
47
|
+
headers: {
|
|
48
|
+
Authorization: `bearer ${accessToken}`
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
return onSuccess(event, {
|
|
53
|
+
tokens,
|
|
54
|
+
user
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { OAuthConfig } from '#auth-utils';
|
|
2
|
+
export interface OAuthStravaConfig {
|
|
3
|
+
/**
|
|
4
|
+
* Strava OAuth Client ID
|
|
5
|
+
* @default process.env.NUXT_OAUTH_STRAVA_CLIENT_ID
|
|
6
|
+
*/
|
|
7
|
+
clientId?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Strava OAuth Client Secret
|
|
10
|
+
* @default process.env.NUXT_OAUTH_STRAVA_CLIENT_SECRET
|
|
11
|
+
*/
|
|
12
|
+
clientSecret?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Strava OAuth Scope
|
|
15
|
+
* @default []
|
|
16
|
+
* @see https://developers.strava.com/docs/authentication/ # Details About Requesting Access
|
|
17
|
+
* @example ['read', 'read_all', 'profile:read_all', 'profile:write', 'activity:read', 'activity:read_all', 'activity:write']
|
|
18
|
+
*/
|
|
19
|
+
scope?: string[];
|
|
20
|
+
/**
|
|
21
|
+
* Redirect URL to allow overriding for situations like prod failing to determine public hostname
|
|
22
|
+
* @default process.env.NUXT_OAUTH_STRAVA_REDIRECT_URL or current URL
|
|
23
|
+
*/
|
|
24
|
+
redirectURL?: string;
|
|
25
|
+
/**
|
|
26
|
+
* To show the authorization prompt to the user, 'force' will always show the prompt
|
|
27
|
+
* @default 'auto'
|
|
28
|
+
* @see https://developers.strava.com/docs/authentication/ # Details About Requesting Access
|
|
29
|
+
*/
|
|
30
|
+
approvalPrompt?: 'auto' | 'force';
|
|
31
|
+
}
|
|
32
|
+
export interface OAuthStravaUser {
|
|
33
|
+
/**
|
|
34
|
+
* The unique identifier of the athlete
|
|
35
|
+
*/
|
|
36
|
+
id: number;
|
|
37
|
+
/**
|
|
38
|
+
* The username of the athlete
|
|
39
|
+
*/
|
|
40
|
+
username: string;
|
|
41
|
+
/**
|
|
42
|
+
* Resource state, indicates level of detail.
|
|
43
|
+
* - Meta (1): Basic information
|
|
44
|
+
* - Summary (2): Summary information
|
|
45
|
+
* - Detail (3): Detailed information
|
|
46
|
+
* @see https://developers.strava.com/docs/reference/#api-models-DetailedAthlete
|
|
47
|
+
*/
|
|
48
|
+
resource_state: 1 | 2 | 3;
|
|
49
|
+
/**
|
|
50
|
+
* The athlete's first name
|
|
51
|
+
*/
|
|
52
|
+
firstname: string;
|
|
53
|
+
/**
|
|
54
|
+
* The athlete's last name
|
|
55
|
+
*/
|
|
56
|
+
lastname: string;
|
|
57
|
+
/**
|
|
58
|
+
* The athlete's bio
|
|
59
|
+
*/
|
|
60
|
+
bio: string;
|
|
61
|
+
/**
|
|
62
|
+
* The athlete's city
|
|
63
|
+
*/
|
|
64
|
+
city: string;
|
|
65
|
+
/**
|
|
66
|
+
* The athlete's state or geographical region
|
|
67
|
+
*/
|
|
68
|
+
state: string;
|
|
69
|
+
/**
|
|
70
|
+
* The athlete's country
|
|
71
|
+
*/
|
|
72
|
+
country: string;
|
|
73
|
+
/**
|
|
74
|
+
* The athlete's sex
|
|
75
|
+
*/
|
|
76
|
+
sex: string;
|
|
77
|
+
/**
|
|
78
|
+
* Whether the athlete has any Summit subscription
|
|
79
|
+
* @see https://developers.strava.com/docs/reference/#api-models-DetailedAthlete
|
|
80
|
+
*/
|
|
81
|
+
summit: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* The time at which the athlete was created
|
|
84
|
+
*/
|
|
85
|
+
created_at: Date;
|
|
86
|
+
/**
|
|
87
|
+
* The time at which the athlete was last updated
|
|
88
|
+
*/
|
|
89
|
+
updated_at: Date;
|
|
90
|
+
/**
|
|
91
|
+
* The athlete's weight
|
|
92
|
+
*/
|
|
93
|
+
weight: number;
|
|
94
|
+
/**
|
|
95
|
+
* URL to a 124x124 pixel profile picture
|
|
96
|
+
*/
|
|
97
|
+
profile_medium: string;
|
|
98
|
+
/**
|
|
99
|
+
* URL to a 62x62 pixel profile picture
|
|
100
|
+
*/
|
|
101
|
+
profile: string;
|
|
102
|
+
/**
|
|
103
|
+
* The athlete's timezone
|
|
104
|
+
*/
|
|
105
|
+
timezone: string;
|
|
106
|
+
}
|
|
107
|
+
export interface OAuthStravaTokens {
|
|
108
|
+
token_type: 'Bearer';
|
|
109
|
+
expires_at: number;
|
|
110
|
+
expires_in: number;
|
|
111
|
+
access_token: string;
|
|
112
|
+
refresh_token: string;
|
|
113
|
+
athlete: OAuthStravaUser;
|
|
114
|
+
error?: string;
|
|
115
|
+
}
|
|
116
|
+
export declare function defineOAuthStravaEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthStravaConfig, OAuthStravaUser>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { eventHandler, getQuery, sendRedirect } from "h3";
|
|
2
|
+
import { withQuery } from "ufo";
|
|
3
|
+
import { defu } from "defu";
|
|
4
|
+
import {
|
|
5
|
+
getOAuthRedirectURL,
|
|
6
|
+
handleAccessTokenErrorResponse,
|
|
7
|
+
handleMissingConfiguration,
|
|
8
|
+
requestAccessToken
|
|
9
|
+
} from "../utils.js";
|
|
10
|
+
import { useRuntimeConfig, createError } from "#imports";
|
|
11
|
+
export function defineOAuthStravaEventHandler({
|
|
12
|
+
config,
|
|
13
|
+
onSuccess,
|
|
14
|
+
onError
|
|
15
|
+
}) {
|
|
16
|
+
return eventHandler(async (event) => {
|
|
17
|
+
config = defu(config, useRuntimeConfig(event).oauth?.strava);
|
|
18
|
+
const query = getQuery(event);
|
|
19
|
+
if (query.error) {
|
|
20
|
+
const error = createError({
|
|
21
|
+
statusCode: 401,
|
|
22
|
+
message: `Strava login failed: ${query.error || "Unknown error"}`,
|
|
23
|
+
data: query
|
|
24
|
+
});
|
|
25
|
+
if (!onError) throw error;
|
|
26
|
+
return onError(event, error);
|
|
27
|
+
}
|
|
28
|
+
if (!config.clientId || !config.clientSecret) {
|
|
29
|
+
return handleMissingConfiguration(
|
|
30
|
+
event,
|
|
31
|
+
"strava",
|
|
32
|
+
["clientId", "clientSecret"],
|
|
33
|
+
onError
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
const authorizationURL = "https://www.strava.com/oauth/authorize";
|
|
37
|
+
const tokenURL = "https://www.strava.com/oauth/token";
|
|
38
|
+
const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
|
|
39
|
+
if (!query.code) {
|
|
40
|
+
return sendRedirect(
|
|
41
|
+
event,
|
|
42
|
+
withQuery(authorizationURL, {
|
|
43
|
+
client_id: config.clientId,
|
|
44
|
+
redirect_uri: redirectURL,
|
|
45
|
+
response_type: "code",
|
|
46
|
+
approval_prompt: config.approvalPrompt || "auto",
|
|
47
|
+
scope: config.scope
|
|
48
|
+
})
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const tokens = await requestAccessToken(tokenURL, {
|
|
52
|
+
body: {
|
|
53
|
+
client_id: config.clientId,
|
|
54
|
+
client_secret: config.clientSecret,
|
|
55
|
+
code: query.code,
|
|
56
|
+
grant_type: "authorization_code",
|
|
57
|
+
redirect_uri: redirectURL
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
if (tokens.error) {
|
|
61
|
+
return handleAccessTokenErrorResponse(event, "strava", tokens, onError);
|
|
62
|
+
}
|
|
63
|
+
const user = await $fetch("https://www.strava.com/api/v3/athlete", {
|
|
64
|
+
headers: {
|
|
65
|
+
Authorization: `Bearer ${tokens.access_token}`
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return onSuccess(event, {
|
|
69
|
+
user,
|
|
70
|
+
tokens
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { OAuthConfig } from '#auth-utils';
|
|
2
|
+
/**
|
|
3
|
+
* WorkOS OAuth Configuration
|
|
4
|
+
* @see https://workos.com/docs/reference/user-management/authentication
|
|
5
|
+
*/
|
|
6
|
+
export interface OAuthWorkOSConfig {
|
|
7
|
+
/**
|
|
8
|
+
* WorkOS OAuth Client ID *required
|
|
9
|
+
* @default process.env.NUXT_OAUTH_WORKOS_CLIENT_ID
|
|
10
|
+
*/
|
|
11
|
+
clientId?: string;
|
|
12
|
+
/**
|
|
13
|
+
* WorkOS OAuth Client Secret (API Key) *required
|
|
14
|
+
* @default process.env.NUXT_OAUTH_WORKOS_CLIENT_SECRET
|
|
15
|
+
*/
|
|
16
|
+
clientSecret?: string;
|
|
17
|
+
/**
|
|
18
|
+
* WorkOS OAuth Organization ID *not required
|
|
19
|
+
* @default process.env.NUXT_OAUTH_WORKOS_ORGANIZATION_ID
|
|
20
|
+
*/
|
|
21
|
+
organizationId?: string;
|
|
22
|
+
/**
|
|
23
|
+
* WorkOS OAuth Connection ID *not required
|
|
24
|
+
* @default process.env.NUXT_OAUTH_WORKOS_CONNECTION_ID
|
|
25
|
+
*/
|
|
26
|
+
connectionId?: string;
|
|
27
|
+
/**
|
|
28
|
+
* WorkOS OAuth screen hint *not required
|
|
29
|
+
* @default 'sign-in'
|
|
30
|
+
*/
|
|
31
|
+
screenHint?: 'sign-in' | 'sign-up';
|
|
32
|
+
/**
|
|
33
|
+
* Redirect URL to to allow overriding for situations like prod failing to determine public hostname
|
|
34
|
+
* @default process.env.NUXT_OAUTH_WORKOS_REDIRECT_URL or current URL
|
|
35
|
+
*/
|
|
36
|
+
redirectURL?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface OAuthWorkOSUser {
|
|
39
|
+
object: 'user';
|
|
40
|
+
id: string;
|
|
41
|
+
email: string;
|
|
42
|
+
first_name: string | null;
|
|
43
|
+
last_name: string | null;
|
|
44
|
+
email_verified: boolean;
|
|
45
|
+
profile_picture_url: string | null;
|
|
46
|
+
created_at: string;
|
|
47
|
+
updated_at: string;
|
|
48
|
+
}
|
|
49
|
+
export type OAuthWorkOSAuthenticationMethod = 'SSO' | 'Password' | 'AppleOAuth' | 'GitHubOAuth' | 'GoogleOAuth' | 'MicrosoftOAuth' | 'MagicAuth' | 'Impersonation';
|
|
50
|
+
export interface OAuthWorkOSAuthenticateResponse {
|
|
51
|
+
user: OAuthWorkOSUser;
|
|
52
|
+
organization_id: string | null;
|
|
53
|
+
access_token: string;
|
|
54
|
+
refresh_token: string;
|
|
55
|
+
error: string | null;
|
|
56
|
+
error_description: string | null;
|
|
57
|
+
authentication_method: OAuthWorkOSAuthenticationMethod;
|
|
58
|
+
}
|
|
59
|
+
export interface OAuthWorkOSTokens {
|
|
60
|
+
access_token: string;
|
|
61
|
+
refresh_token: string;
|
|
62
|
+
}
|
|
63
|
+
export declare function defineOAuthWorkOSEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthWorkOSConfig, OAuthWorkOSUser, OAuthWorkOSTokens>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { eventHandler, getQuery, sendRedirect, getRequestIP, getRequestHeader } from "h3";
|
|
2
|
+
import { withQuery } from "ufo";
|
|
3
|
+
import { defu } from "defu";
|
|
4
|
+
import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
|
|
5
|
+
import { useRuntimeConfig } from "#imports";
|
|
6
|
+
export function defineOAuthWorkOSEventHandler({ config, onSuccess, onError }) {
|
|
7
|
+
return eventHandler(async (event) => {
|
|
8
|
+
config = defu(config, useRuntimeConfig(event).oauth?.workos, { screen_hint: "sign-in" });
|
|
9
|
+
if (!config.clientId || !config.clientSecret) {
|
|
10
|
+
return handleMissingConfiguration(event, "workos", ["clientId", "clientSecret"], onError);
|
|
11
|
+
}
|
|
12
|
+
const query = getQuery(event);
|
|
13
|
+
const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
|
|
14
|
+
if (query.error) {
|
|
15
|
+
return handleAccessTokenErrorResponse(event, "workos", query, onError);
|
|
16
|
+
}
|
|
17
|
+
if (!query.code) {
|
|
18
|
+
return sendRedirect(
|
|
19
|
+
event,
|
|
20
|
+
withQuery("https://api.workos.com/user_management/authorize", {
|
|
21
|
+
response_type: "code",
|
|
22
|
+
provider: "authkit",
|
|
23
|
+
client_id: config.clientId,
|
|
24
|
+
redirect_uri: redirectURL || void 0,
|
|
25
|
+
connection_id: config.connectionId || void 0,
|
|
26
|
+
screen_hint: config.screenHint || "sign-in",
|
|
27
|
+
organization_id: config.organizationId || void 0
|
|
28
|
+
})
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
const authenticateResponse = await requestAccessToken("https://api.workos.com/user_management/authenticate", {
|
|
32
|
+
headers: {
|
|
33
|
+
"Content-Type": "application/json"
|
|
34
|
+
},
|
|
35
|
+
body: {
|
|
36
|
+
grant_type: "authorization_code",
|
|
37
|
+
client_id: config.clientId,
|
|
38
|
+
client_secret: config.clientSecret,
|
|
39
|
+
redirect_uri: redirectURL,
|
|
40
|
+
ip_address: getRequestIP(event),
|
|
41
|
+
user_agent: getRequestHeader(event, "user-agent"),
|
|
42
|
+
code: query.code
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
if (authenticateResponse.error) {
|
|
46
|
+
return handleAccessTokenErrorResponse(event, "workos", authenticateResponse, onError);
|
|
47
|
+
}
|
|
48
|
+
return onSuccess(event, {
|
|
49
|
+
tokens: { access_token: authenticateResponse.access_token, refresh_token: authenticateResponse.refresh_token },
|
|
50
|
+
user: authenticateResponse.user
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -35,7 +35,7 @@ export function defineOAuthXSUAAEventHandler({ config, onSuccess, onError }) {
|
|
|
35
35
|
}
|
|
36
36
|
});
|
|
37
37
|
if (tokens.error) {
|
|
38
|
-
return handleAccessTokenErrorResponse(event, "
|
|
38
|
+
return handleAccessTokenErrorResponse(event, "xsuaa", tokens, onError);
|
|
39
39
|
}
|
|
40
40
|
const tokenType = tokens.token_type;
|
|
41
41
|
const accessToken = tokens.access_token;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
2
2
|
import type { WebAuthnUser, WebAuthnRegisterEventHandlerOptions } from '#auth-utils';
|
|
3
|
+
import type { ValidateUserFunction } from '~/src/runtime/types/webauthn';
|
|
3
4
|
export declare function defineWebAuthnRegisterEventHandler<T extends WebAuthnUser>({ storeChallenge, getChallenge, getOptions, validateUser, excludeCredentials, onSuccess, onError, }: WebAuthnRegisterEventHandlerOptions<T>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void | import("@simplewebauthn/server").VerifiedRegistrationResponse | {
|
|
4
5
|
creationOptions: import("@simplewebauthn/types").PublicKeyCredentialCreationOptionsJSON;
|
|
5
6
|
attemptId: string;
|
|
6
7
|
}>>;
|
|
7
|
-
export declare function validateUserData<T>(
|
|
8
|
+
export declare function validateUserData<T>(userBody: WebAuthnUser, event: H3Event, fn: ValidateUserFunction<T>): Promise<T>;
|
|
@@ -23,7 +23,7 @@ export function defineWebAuthnRegisterEventHandler({
|
|
|
23
23
|
});
|
|
24
24
|
let user = body.user;
|
|
25
25
|
if (validateUser) {
|
|
26
|
-
user = await validateUserData(body.user, validateUser);
|
|
26
|
+
user = await validateUserData(body.user, event, validateUser);
|
|
27
27
|
}
|
|
28
28
|
const _config = defu(await getOptions?.(event, body) ?? {}, useRuntimeConfig(event).webauthn.register, {
|
|
29
29
|
rpID: url.hostname,
|
|
@@ -97,24 +97,24 @@ export function defineWebAuthnRegisterEventHandler({
|
|
|
97
97
|
}
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
|
-
export async function validateUserData(
|
|
100
|
+
export async function validateUserData(userBody, event, fn) {
|
|
101
101
|
try {
|
|
102
|
-
const res = await fn(
|
|
102
|
+
const res = await fn(userBody, event);
|
|
103
103
|
if (res === false) {
|
|
104
104
|
throw createUserValidationError();
|
|
105
105
|
}
|
|
106
106
|
if (res === true) {
|
|
107
|
-
return
|
|
107
|
+
return userBody;
|
|
108
108
|
}
|
|
109
|
-
return res ??
|
|
109
|
+
return res ?? userBody;
|
|
110
110
|
} catch (error) {
|
|
111
111
|
throw createUserValidationError(error);
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
function createUserValidationError(validateError) {
|
|
115
115
|
throw createError({
|
|
116
|
-
status: 400,
|
|
117
|
-
message: "User Validation Error",
|
|
116
|
+
status: validateError?.statusCode || 400,
|
|
117
|
+
message: validateError?.message || "User Validation Error",
|
|
118
118
|
data: validateError
|
|
119
119
|
});
|
|
120
120
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { H3Event, H3Error } from 'h3';
|
|
2
|
-
export type OAuthProvider = 'auth0' | 'authentik' | 'battledotnet' | 'cognito' | 'discord' | 'dropbox' | 'facebook' | 'github' | 'gitlab' | 'google' | 'instagram' | 'keycloak' | 'linear' | 'linkedin' | 'microsoft' | 'paypal' | 'polar' | 'spotify' | 'steam' | 'tiktok' | 'twitch' | 'vk' | 'x' | 'xsuaa' | 'yandex' | 'zitadel' | (string & {});
|
|
2
|
+
export type OAuthProvider = 'auth0' | 'authentik' | 'battledotnet' | 'cognito' | 'discord' | 'dropbox' | 'facebook' | 'github' | 'gitlab' | 'google' | 'instagram' | 'keycloak' | 'linear' | 'linkedin' | 'microsoft' | 'paypal' | 'polar' | 'spotify' | 'seznam' | 'steam' | 'strava' | 'tiktok' | 'twitch' | 'vk' | 'workos' | 'x' | 'xsuaa' | 'yandex' | 'zitadel' | (string & {});
|
|
3
3
|
export type OnError = (event: H3Event, error: H3Error) => Promise<void> | void;
|
|
4
4
|
export interface OAuthConfig<TConfig, TUser = any, TTokens = any> {
|
|
5
5
|
config?: TConfig;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AuthenticationResponseJSON, AuthenticatorTransportFuture, RegistrationResponseJSON } from '@simplewebauthn/types';
|
|
2
2
|
import type { Ref } from 'vue';
|
|
3
|
-
import type { H3Event, H3Error,
|
|
3
|
+
import type { H3Event, H3Error, ValidateResult } from 'h3';
|
|
4
4
|
import type { GenerateAuthenticationOptionsOpts, GenerateRegistrationOptionsOpts, VerifiedAuthenticationResponse, VerifiedRegistrationResponse } from '@simplewebauthn/server';
|
|
5
5
|
export interface WebAuthnCredential {
|
|
6
6
|
id: string;
|
|
@@ -36,13 +36,14 @@ export type RegistrationBody<T extends WebAuthnUser> = {
|
|
|
36
36
|
attemptId: string;
|
|
37
37
|
response: RegistrationResponseJSON;
|
|
38
38
|
};
|
|
39
|
+
export type ValidateUserFunction<T> = (userBody: WebAuthnUser, event: H3Event) => ValidateResult<T> | Promise<ValidateResult<T>>;
|
|
39
40
|
export type WebAuthnRegisterEventHandlerOptions<T extends WebAuthnUser> = WebAuthnEventHandlerBase<{
|
|
40
41
|
user: T;
|
|
41
42
|
credential: WebAuthnCredential;
|
|
42
43
|
registrationInfo: Exclude<VerifiedRegistrationResponse['registrationInfo'], undefined>;
|
|
43
44
|
}> & {
|
|
44
45
|
getOptions?: (event: H3Event, body: RegistrationBody<T>) => Partial<GenerateRegistrationOptionsOpts> | Promise<Partial<GenerateRegistrationOptionsOpts>>;
|
|
45
|
-
validateUser?:
|
|
46
|
+
validateUser?: ValidateUserFunction<T>;
|
|
46
47
|
excludeCredentials?: (event: H3Event, userName: string) => CredentialsList | Promise<CredentialsList>;
|
|
47
48
|
};
|
|
48
49
|
export type AuthenticationBody = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-auth-utils",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
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@9.13.
|
|
11
|
+
"packageManager": "pnpm@9.13.2",
|
|
12
12
|
"exports": {
|
|
13
13
|
".": {
|
|
14
14
|
"types": "./dist/types.d.ts",
|