nuxt-auth-utils 0.5.21 → 0.5.23
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 +2 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +28 -4
- package/dist/runtime/server/lib/oauth/github.js +6 -1
- package/dist/runtime/server/lib/oauth/okta.d.ts +88 -0
- package/dist/runtime/server/lib/oauth/okta.js +171 -0
- package/dist/runtime/server/lib/oauth/ory.d.ts +51 -0
- package/dist/runtime/server/lib/oauth/ory.js +101 -0
- package/dist/runtime/server/lib/oauth/roblox.d.ts +126 -0
- package/dist/runtime/server/lib/oauth/roblox.js +64 -0
- package/dist/runtime/server/lib/oauth/zitadel.js +23 -10
- package/dist/runtime/server/lib/utils.d.ts +1 -1
- package/dist/runtime/server/utils/session.js +3 -0
- package/dist/runtime/types/oauth-config.d.ts +1 -1
- package/package.json +11 -11
package/README.md
CHANGED
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -74,7 +74,7 @@ const module = defineNuxtModule({
|
|
|
74
74
|
const envSessionPassword = `${runtimeConfig.nitro?.envPrefix || "NUXT_"}SESSION_PASSWORD`;
|
|
75
75
|
runtimeConfig.session = defu(runtimeConfig.session, {
|
|
76
76
|
name: "nuxt-session",
|
|
77
|
-
password:
|
|
77
|
+
password: "",
|
|
78
78
|
cookie: {
|
|
79
79
|
sameSite: "lax"
|
|
80
80
|
}
|
|
@@ -82,14 +82,14 @@ const module = defineNuxtModule({
|
|
|
82
82
|
runtimeConfig.hash = defu(runtimeConfig.hash, {
|
|
83
83
|
scrypt: options.hash?.scrypt
|
|
84
84
|
});
|
|
85
|
-
if (nuxt.options.dev && !
|
|
86
|
-
|
|
85
|
+
if (nuxt.options.dev && !process.env[envSessionPassword]) {
|
|
86
|
+
const password = process.env[envSessionPassword] = randomUUID().replace(/-/g, "");
|
|
87
87
|
const envPath = join(nuxt.options.rootDir, ".env");
|
|
88
88
|
const envContent = await readFile(envPath, "utf-8").catch(() => "");
|
|
89
89
|
if (!envContent.includes(envSessionPassword)) {
|
|
90
90
|
await writeFile(
|
|
91
91
|
envPath,
|
|
92
|
-
`${envContent ? envContent + "\n" : envContent}${envSessionPassword}=${
|
|
92
|
+
`${envContent ? envContent + "\n" : envContent}${envSessionPassword}=${password}`,
|
|
93
93
|
"utf-8"
|
|
94
94
|
);
|
|
95
95
|
}
|
|
@@ -353,6 +353,30 @@ const module = defineNuxtModule({
|
|
|
353
353
|
redirectURL: "",
|
|
354
354
|
scope: ""
|
|
355
355
|
});
|
|
356
|
+
runtimeConfig.oauth.roblox = defu(runtimeConfig.oauth.roblox, {
|
|
357
|
+
clientId: "",
|
|
358
|
+
clientSecret: "",
|
|
359
|
+
redirectURL: "",
|
|
360
|
+
scope: ""
|
|
361
|
+
});
|
|
362
|
+
runtimeConfig.oauth.okta = defu(runtimeConfig.oauth.okta, {
|
|
363
|
+
clientId: "",
|
|
364
|
+
clientSecret: "",
|
|
365
|
+
domain: "",
|
|
366
|
+
audience: "",
|
|
367
|
+
scope: [],
|
|
368
|
+
redirectURL: ""
|
|
369
|
+
});
|
|
370
|
+
runtimeConfig.oauth.ory = defu(runtimeConfig.oauth.ory, {
|
|
371
|
+
clientId: "",
|
|
372
|
+
clientSecret: "",
|
|
373
|
+
sdkURL: "",
|
|
374
|
+
redirectURL: "",
|
|
375
|
+
scope: [],
|
|
376
|
+
authorizationURL: "",
|
|
377
|
+
tokenURL: "",
|
|
378
|
+
userURL: ""
|
|
379
|
+
});
|
|
356
380
|
}
|
|
357
381
|
});
|
|
358
382
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { eventHandler, getQuery, sendRedirect, createError } from "h3";
|
|
2
2
|
import { withQuery } from "ufo";
|
|
3
3
|
import { defu } from "defu";
|
|
4
|
-
import {
|
|
4
|
+
import { getOAuthRedirectURL, handleAccessTokenErrorResponse, handleInvalidState, handleMissingConfiguration, handleState, requestAccessToken } from "../utils.js";
|
|
5
5
|
import { useRuntimeConfig } from "#imports";
|
|
6
6
|
export function defineOAuthGitHubEventHandler({ config, onSuccess, onError }) {
|
|
7
7
|
return eventHandler(async (event) => {
|
|
@@ -25,6 +25,7 @@ export function defineOAuthGitHubEventHandler({ config, onSuccess, onError }) {
|
|
|
25
25
|
return handleMissingConfiguration(event, "github", ["clientId", "clientSecret"], onError);
|
|
26
26
|
}
|
|
27
27
|
const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
|
|
28
|
+
const state = await handleState(event);
|
|
28
29
|
if (!query.code) {
|
|
29
30
|
config.scope = config.scope || [];
|
|
30
31
|
if (config.emailRequired && !config.scope.includes("user:email")) {
|
|
@@ -36,10 +37,14 @@ export function defineOAuthGitHubEventHandler({ config, onSuccess, onError }) {
|
|
|
36
37
|
client_id: config.clientId,
|
|
37
38
|
redirect_uri: redirectURL,
|
|
38
39
|
scope: config.scope.join(" "),
|
|
40
|
+
state,
|
|
39
41
|
...config.authorizationParams
|
|
40
42
|
})
|
|
41
43
|
);
|
|
42
44
|
}
|
|
45
|
+
if (query.state !== state) {
|
|
46
|
+
return handleInvalidState(event, "github", onError);
|
|
47
|
+
}
|
|
43
48
|
const tokens = await requestAccessToken(config.tokenURL, {
|
|
44
49
|
body: {
|
|
45
50
|
grant_type: "authorization_code",
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
2
|
+
import type { OAuthConfig } from '#auth-utils';
|
|
3
|
+
export interface OpenIdConfig {
|
|
4
|
+
authorization_endpoint: string;
|
|
5
|
+
token_endpoint: string;
|
|
6
|
+
userinfo_endpoint: string;
|
|
7
|
+
end_session_endpoint?: string;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
export interface OAuthConfigExt<TConfig, TResult = {
|
|
11
|
+
user: Record<string, unknown>;
|
|
12
|
+
tokens: Record<string, unknown>;
|
|
13
|
+
openIdConfig: OpenIdConfig;
|
|
14
|
+
}> extends OAuthConfig<TConfig, TResult> {
|
|
15
|
+
config?: TConfig;
|
|
16
|
+
onSuccess: (event: H3Event, result: TResult) => Promise<void> | void;
|
|
17
|
+
onError?: (event: H3Event, error: unknown) => Promise<void> | void;
|
|
18
|
+
}
|
|
19
|
+
export interface OAuthOktaConfig {
|
|
20
|
+
/**
|
|
21
|
+
* Okta OAuth Client ID
|
|
22
|
+
* @default process.env.NUXT_OAUTH_OKTA_CLIENT_ID
|
|
23
|
+
*/
|
|
24
|
+
clientId?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Okta OAuth Client Secret
|
|
27
|
+
* @default process.env.NUXT_OAUTH_OKTA_CLIENT_SECRET
|
|
28
|
+
*/
|
|
29
|
+
clientSecret?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Okta OAuth Issuer
|
|
32
|
+
* @default process.env.NUXT_OAUTH_OKTA_DOMAIN
|
|
33
|
+
*/
|
|
34
|
+
domain?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Okta OAuth Authorization Server
|
|
37
|
+
* @see https://developer.okta.com/docs/guides/customize-authz-server/main/#create-an-authorization-server
|
|
38
|
+
* @default process.env.NUXT_OAUTH_OKTA_AUTHORIZATION_SERVER
|
|
39
|
+
*/
|
|
40
|
+
authorizationServer?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Okta OAuth Audience
|
|
43
|
+
* @default process.env.NUXT_OAUTH_OKTA_AUDIENCE
|
|
44
|
+
*/
|
|
45
|
+
audience?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Okta OAuth Scope
|
|
48
|
+
* @default []
|
|
49
|
+
* @see https://okta.com/docs/get-started/apis/scopes/openid-connect-scopes
|
|
50
|
+
* @example ['openid']
|
|
51
|
+
*/
|
|
52
|
+
scope?: string | string[];
|
|
53
|
+
/**
|
|
54
|
+
* Require email from user, adds the ['email'] scope if not present
|
|
55
|
+
* @default false
|
|
56
|
+
*/
|
|
57
|
+
emailRequired?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Maximum Authentication Age. If the elapsed time is greater than this value, the OP must attempt to actively re-authenticate the end-user.
|
|
60
|
+
* @default 0
|
|
61
|
+
* @see https://okta.com/docs/authenticate/login/max-age-reauthentication
|
|
62
|
+
*/
|
|
63
|
+
maxAge?: number;
|
|
64
|
+
/**
|
|
65
|
+
* Login connection. If no connection is specified, it will redirect to the standard Okta login page and show the Login Widget.
|
|
66
|
+
* @default ''
|
|
67
|
+
* @see https://okta.com/docs/api/authentication#social
|
|
68
|
+
* @example 'github'
|
|
69
|
+
*/
|
|
70
|
+
connection?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Extra authorization parameters to provide to the authorization URL
|
|
73
|
+
* @see https://okta.com/docs/api/authentication#social
|
|
74
|
+
* @example { display: 'popup' }
|
|
75
|
+
*/
|
|
76
|
+
authorizationParams?: Record<string, string>;
|
|
77
|
+
/**
|
|
78
|
+
* Redirect URL to to allow overriding for situations like prod failing to determine public hostname
|
|
79
|
+
* @default process.env.NUXT_OAUTH_OKTA_REDIRECT_URL or current URL
|
|
80
|
+
*/
|
|
81
|
+
redirectURL?: string;
|
|
82
|
+
/**
|
|
83
|
+
* OpenID Configuration cache TTL in milliseconds
|
|
84
|
+
* @default 86400000 (24 hours)
|
|
85
|
+
*/
|
|
86
|
+
openIdConfigCacheTTL?: number;
|
|
87
|
+
}
|
|
88
|
+
export declare function defineOAuthOktaEventHandler({ config, onSuccess, onError }: OAuthConfigExt<OAuthOktaConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { eventHandler, getQuery, sendRedirect, createError, deleteCookie } from "h3";
|
|
2
|
+
import { withQuery } from "ufo";
|
|
3
|
+
import { defu } from "defu";
|
|
4
|
+
import { handleMissingConfiguration, handleState, handleInvalidState, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
|
|
5
|
+
import { useRuntimeConfig } from "#imports";
|
|
6
|
+
const openIdConfigCache = /* @__PURE__ */ new Map();
|
|
7
|
+
const DEFAULT_CACHE_TTL = 1e3 * 60 * 60 * 24;
|
|
8
|
+
export function defineOAuthOktaEventHandler({ config, onSuccess, onError }) {
|
|
9
|
+
function normalizeScope(scope, emailRequired) {
|
|
10
|
+
let result;
|
|
11
|
+
if (!scope || typeof scope === "string" && scope.trim() === "") {
|
|
12
|
+
result = ["openid", "email", "profile"];
|
|
13
|
+
} else if (typeof scope === "string") {
|
|
14
|
+
result = scope.split(/[,| ]+/).filter(Boolean);
|
|
15
|
+
} else if (Array.isArray(scope) && scope.length > 0) {
|
|
16
|
+
result = scope;
|
|
17
|
+
} else {
|
|
18
|
+
result = ["openid", "email", "profile"];
|
|
19
|
+
}
|
|
20
|
+
result = [...new Set(result)];
|
|
21
|
+
if (emailRequired && !result.includes("email")) {
|
|
22
|
+
result.push("email");
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
return eventHandler(async (event) => {
|
|
27
|
+
const runtimeConfig = useRuntimeConfig(event);
|
|
28
|
+
config = defu(config, runtimeConfig.oauth?.okta, {
|
|
29
|
+
authorizationParams: {}
|
|
30
|
+
});
|
|
31
|
+
if (!config.clientId || !config.clientSecret || !config.domain || typeof config.domain !== "string" || !config.domain.trim()) {
|
|
32
|
+
return handleMissingConfiguration(event, "okta", ["clientId", "clientSecret", "domain"], onError);
|
|
33
|
+
}
|
|
34
|
+
const query = getQuery(event);
|
|
35
|
+
if (query.error) {
|
|
36
|
+
const error = createError({
|
|
37
|
+
statusCode: 401,
|
|
38
|
+
message: `Okta login failed: ${query.error || "Unknown error"} - ${query.error_description || ""}`,
|
|
39
|
+
data: query
|
|
40
|
+
});
|
|
41
|
+
if (!onError) throw error;
|
|
42
|
+
return onError(event, error);
|
|
43
|
+
}
|
|
44
|
+
config.scope = normalizeScope(config.scope, config.emailRequired);
|
|
45
|
+
const getOpenIdConfig = async (openIdConfigurationUrl2, event2) => {
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
const cacheTTL = config?.openIdConfigCacheTTL || DEFAULT_CACHE_TTL;
|
|
48
|
+
const cached = openIdConfigCache.get(openIdConfigurationUrl2);
|
|
49
|
+
if (cached) {
|
|
50
|
+
if (cached.expiresAt > now) {
|
|
51
|
+
return cached.data;
|
|
52
|
+
} else {
|
|
53
|
+
openIdConfigCache.delete(openIdConfigurationUrl2);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
let openIdConfig2 = null;
|
|
57
|
+
try {
|
|
58
|
+
openIdConfig2 = await $fetch(openIdConfigurationUrl2);
|
|
59
|
+
if (openIdConfig2) {
|
|
60
|
+
openIdConfigCache.set(openIdConfigurationUrl2, {
|
|
61
|
+
data: openIdConfig2,
|
|
62
|
+
expiresAt: now + cacheTTL
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if (config && config.domain) {
|
|
67
|
+
const authz = config.authorizationServer;
|
|
68
|
+
openIdConfig2 = {
|
|
69
|
+
authorization_endpoint: authz ? `https://${config.domain}/oauth2/${authz}/v1/authorize` : `https://${config.domain}/oauth2/v1/authorize`,
|
|
70
|
+
token_endpoint: authz ? `https://${config.domain}/oauth2/${authz}/v1/token` : `https://${config.domain}/oauth2/v1/token`,
|
|
71
|
+
userinfo_endpoint: authz ? `https://${config.domain}/oauth2/${authz}/v1/userinfo` : `https://${config.domain}/oauth2/v1/userinfo`,
|
|
72
|
+
end_session_endpoint: void 0
|
|
73
|
+
};
|
|
74
|
+
openIdConfigCache.set(openIdConfigurationUrl2, {
|
|
75
|
+
data: openIdConfig2,
|
|
76
|
+
expiresAt: now + Math.min(cacheTTL / 24, 1e3 * 60 * 60)
|
|
77
|
+
// 1 hour or 1/24th of configured TTL, whichever is smaller
|
|
78
|
+
});
|
|
79
|
+
} else {
|
|
80
|
+
console.error("Failed to fetch Okta OpenID configuration. Please check your Okta domain and network connectivity:", error);
|
|
81
|
+
const err = createError({
|
|
82
|
+
statusCode: 500,
|
|
83
|
+
message: "Could not get Okta OpenID configuration. Please verify that your Okta domain is correct and reachable, and that the OpenID configuration endpoint is accessible.",
|
|
84
|
+
data: error
|
|
85
|
+
});
|
|
86
|
+
if (onError) await onError(event2, err);
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return openIdConfig2;
|
|
91
|
+
};
|
|
92
|
+
const authServer = config.authorizationServer;
|
|
93
|
+
const openIdConfigurationUrl = authServer ? `https://${config.domain}/oauth2/${authServer}/.well-known/openid-configuration` : `https://${config.domain}/.well-known/openid-configuration`;
|
|
94
|
+
const openIdConfig = await getOpenIdConfig(openIdConfigurationUrl, event);
|
|
95
|
+
const authorizationURL = openIdConfig.authorization_endpoint;
|
|
96
|
+
const tokenURL = openIdConfig.token_endpoint;
|
|
97
|
+
const userInfoUrl = openIdConfig.userinfo_endpoint;
|
|
98
|
+
const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
|
|
99
|
+
const state = await handleState(event);
|
|
100
|
+
if (!query.code) {
|
|
101
|
+
return sendRedirect(
|
|
102
|
+
event,
|
|
103
|
+
withQuery(authorizationURL, {
|
|
104
|
+
response_type: "code",
|
|
105
|
+
client_id: config.clientId,
|
|
106
|
+
redirect_uri: redirectURL,
|
|
107
|
+
scope: config.scope.join(" "),
|
|
108
|
+
audience: config.audience || "",
|
|
109
|
+
max_age: config.maxAge || 0,
|
|
110
|
+
connection: config.connection || "",
|
|
111
|
+
state,
|
|
112
|
+
...config.authorizationParams
|
|
113
|
+
})
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
if (query.state !== state) {
|
|
117
|
+
deleteCookie(event, "nuxt-auth-state");
|
|
118
|
+
return handleInvalidState(event, "okta", onError);
|
|
119
|
+
}
|
|
120
|
+
const tokens = await requestAccessToken(tokenURL, {
|
|
121
|
+
headers: {
|
|
122
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
123
|
+
},
|
|
124
|
+
body: {
|
|
125
|
+
response_type: "code",
|
|
126
|
+
grant_type: "authorization_code",
|
|
127
|
+
client_id: config.clientId,
|
|
128
|
+
client_secret: config.clientSecret,
|
|
129
|
+
scope: config.scope?.join(" "),
|
|
130
|
+
redirect_uri: redirectURL,
|
|
131
|
+
code: query.code,
|
|
132
|
+
state: query.state
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
if (tokens.error) {
|
|
136
|
+
return handleAccessTokenErrorResponse(event, "okta", tokens, onError);
|
|
137
|
+
}
|
|
138
|
+
if (!tokens.access_token || typeof tokens.access_token !== "string" || !tokens.token_type || typeof tokens.token_type !== "string") {
|
|
139
|
+
const err = createError({
|
|
140
|
+
statusCode: 400,
|
|
141
|
+
message: "Invalid token response from Okta",
|
|
142
|
+
data: tokens
|
|
143
|
+
});
|
|
144
|
+
if (onError) return onError(event, err);
|
|
145
|
+
throw err;
|
|
146
|
+
}
|
|
147
|
+
const tokenType = tokens.token_type;
|
|
148
|
+
const accessToken = tokens.access_token;
|
|
149
|
+
let user;
|
|
150
|
+
try {
|
|
151
|
+
user = await $fetch(userInfoUrl, {
|
|
152
|
+
headers: {
|
|
153
|
+
Authorization: `${tokenType} ${accessToken}`
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
} catch (error) {
|
|
157
|
+
const err = createError({
|
|
158
|
+
statusCode: 410,
|
|
159
|
+
message: `Could not get Okta user info - ${error instanceof Error ? error.message : String(error)}`,
|
|
160
|
+
data: tokens
|
|
161
|
+
});
|
|
162
|
+
if (onError) return onError(event, err);
|
|
163
|
+
throw err;
|
|
164
|
+
}
|
|
165
|
+
return onSuccess(event, {
|
|
166
|
+
tokens,
|
|
167
|
+
user,
|
|
168
|
+
openIdConfig
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { OAuthConfig } from '#auth-utils';
|
|
2
|
+
/**
|
|
3
|
+
* Ory OAuth2
|
|
4
|
+
* @see https://www.ory.sh/docs/oauth2-oidc/authorization-code-flow
|
|
5
|
+
*/
|
|
6
|
+
export interface OAuthOryConfig {
|
|
7
|
+
/**
|
|
8
|
+
* Ory OAuth Client ID
|
|
9
|
+
* @default process.env.NUXT_OAUTH_ORY_CLIENT_ID
|
|
10
|
+
*/
|
|
11
|
+
clientId?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Ory OAuth Client Secret
|
|
14
|
+
* @default process.env.NUXT_OAUTH_ORY_CLIENT_SECRET
|
|
15
|
+
*/
|
|
16
|
+
clientSecret?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Ory OAuth SDK URL
|
|
19
|
+
* @default "https://playground.projects.oryapis.com" || process.env.NUXT_OAUTH_ORY_SDK_URL
|
|
20
|
+
*/
|
|
21
|
+
sdkURL?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Ory OAuth Scope
|
|
24
|
+
* @default ['openid', 'offline']
|
|
25
|
+
* @see https://www.ory.sh/docs/oauth2-oidc/openid-connect-claims-scope-custom
|
|
26
|
+
* @example ['openid', 'offline', 'email']
|
|
27
|
+
*/
|
|
28
|
+
scope?: string[] | string;
|
|
29
|
+
/**
|
|
30
|
+
* Ory OAuth Authorization URL
|
|
31
|
+
* @default '/oauth2/auth'
|
|
32
|
+
*/
|
|
33
|
+
authorizationURL?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Ory OAuth Token URL
|
|
36
|
+
* @default '/oauth2/token'
|
|
37
|
+
*/
|
|
38
|
+
tokenURL?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Extra authorization parameters to provide to the authorization URL
|
|
41
|
+
* @example { allow_signup: 'true' }
|
|
42
|
+
*/
|
|
43
|
+
authorizationParams?: Record<string, string>;
|
|
44
|
+
/**
|
|
45
|
+
* Ory OAuth Userinfo URL
|
|
46
|
+
* @see https://www.ory.sh/docs/oauth2-oidc/userinfo-oidc
|
|
47
|
+
* @default '/userinfo'
|
|
48
|
+
*/
|
|
49
|
+
userURL?: string;
|
|
50
|
+
}
|
|
51
|
+
export declare function defineOAuthOryEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthOryConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { eventHandler, getQuery, sendRedirect } from "h3";
|
|
2
|
+
import { withQuery } from "ufo";
|
|
3
|
+
import { defu } from "defu";
|
|
4
|
+
import {
|
|
5
|
+
getOAuthRedirectURL,
|
|
6
|
+
handleAccessTokenErrorResponse,
|
|
7
|
+
handleInvalidState,
|
|
8
|
+
handleMissingConfiguration,
|
|
9
|
+
handlePkceVerifier,
|
|
10
|
+
handleState,
|
|
11
|
+
requestAccessToken
|
|
12
|
+
} from "../utils.js";
|
|
13
|
+
import { createError, useRuntimeConfig } from "#imports";
|
|
14
|
+
export function defineOAuthOryEventHandler({ config, onSuccess, onError }) {
|
|
15
|
+
return eventHandler(async (event) => {
|
|
16
|
+
config = defu(config, useRuntimeConfig(event).oauth?.ory, {
|
|
17
|
+
scope: ["openid", "offline"],
|
|
18
|
+
sdkURL: "https://playground.projects.oryapis.com",
|
|
19
|
+
authorizationURL: "/oauth2/auth",
|
|
20
|
+
tokenURL: "/oauth2/token",
|
|
21
|
+
userURL: "/userinfo",
|
|
22
|
+
authorizationParams: {}
|
|
23
|
+
});
|
|
24
|
+
const query = getQuery(event);
|
|
25
|
+
if (query.error) {
|
|
26
|
+
const error = createError({
|
|
27
|
+
statusCode: 401,
|
|
28
|
+
message: `Ory login failed: ${query.error || "Unknown error"}`,
|
|
29
|
+
data: query
|
|
30
|
+
});
|
|
31
|
+
if (!onError) throw error;
|
|
32
|
+
return onError(event, error);
|
|
33
|
+
}
|
|
34
|
+
if (!config.clientId || !config.sdkURL) {
|
|
35
|
+
return handleMissingConfiguration(event, "ory", ["clientId", "sdkURL"], onError);
|
|
36
|
+
}
|
|
37
|
+
const redirectURL = getOAuthRedirectURL(event);
|
|
38
|
+
if (Array.isArray(config.scope)) {
|
|
39
|
+
config.scope = Array.from(new Set(config.scope)).join(" ");
|
|
40
|
+
}
|
|
41
|
+
const verifier = await handlePkceVerifier(event);
|
|
42
|
+
const state = await handleState(event);
|
|
43
|
+
if (!query.code) {
|
|
44
|
+
const authorizationURL = `${config.sdkURL}${config.authorizationURL}`;
|
|
45
|
+
return sendRedirect(
|
|
46
|
+
event,
|
|
47
|
+
withQuery(authorizationURL, {
|
|
48
|
+
client_id: config.clientId,
|
|
49
|
+
response_type: "code",
|
|
50
|
+
redirect_uri: redirectURL,
|
|
51
|
+
scope: config.scope,
|
|
52
|
+
state,
|
|
53
|
+
code_challenge: verifier.code_challenge,
|
|
54
|
+
code_challenge_method: verifier.code_challenge_method,
|
|
55
|
+
...config.authorizationParams
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
if (query.state !== state) {
|
|
60
|
+
handleInvalidState(event, "ory", onError);
|
|
61
|
+
}
|
|
62
|
+
const tokenURL = `${config.sdkURL}${config.tokenURL}`;
|
|
63
|
+
const tokens = await requestAccessToken(tokenURL, {
|
|
64
|
+
body: {
|
|
65
|
+
grant_type: "authorization_code",
|
|
66
|
+
client_id: config.clientId,
|
|
67
|
+
code: query.code,
|
|
68
|
+
redirect_uri: redirectURL,
|
|
69
|
+
scope: config.scope,
|
|
70
|
+
code_verifier: verifier.code_verifier
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
if (tokens.error) {
|
|
74
|
+
return handleAccessTokenErrorResponse(event, "ory", tokens, onError);
|
|
75
|
+
}
|
|
76
|
+
const tokenType = tokens.token_type;
|
|
77
|
+
const accessToken = tokens.access_token;
|
|
78
|
+
const userURL = `${config.sdkURL}${config.userURL}`;
|
|
79
|
+
const user = await $fetch(userURL, {
|
|
80
|
+
headers: {
|
|
81
|
+
"User-Agent": `Ory-${config.clientId}`,
|
|
82
|
+
"Authorization": `${tokenType} ${accessToken}`
|
|
83
|
+
}
|
|
84
|
+
}).catch((error) => {
|
|
85
|
+
return { error };
|
|
86
|
+
});
|
|
87
|
+
if (user.error) {
|
|
88
|
+
const error = createError({
|
|
89
|
+
statusCode: 401,
|
|
90
|
+
message: `Ory userinfo failed: ${user.error || "Unknown error"}`,
|
|
91
|
+
data: user
|
|
92
|
+
});
|
|
93
|
+
if (!onError) throw error;
|
|
94
|
+
return onError(event, error);
|
|
95
|
+
}
|
|
96
|
+
return onSuccess(event, {
|
|
97
|
+
tokens,
|
|
98
|
+
user
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { OAuthConfig } from '#auth-utils';
|
|
2
|
+
export interface OAuthRobloxConfig {
|
|
3
|
+
/**
|
|
4
|
+
* Roblox OAuth Client ID
|
|
5
|
+
* @default process.env.NUXT_OAUTH_ROBLOX_CLIENT_ID
|
|
6
|
+
*/
|
|
7
|
+
clientId?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Roblox OAuth Client Secret
|
|
10
|
+
* @default process.env.NUXT_OAUTH_ROBLOX_CLIENT_SECRET
|
|
11
|
+
*/
|
|
12
|
+
clientSecret?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Roblox OAuth Scope
|
|
15
|
+
* Some scopes and claims listed are only available to official Roblox apps, e.g. email
|
|
16
|
+
* @default ['openid', 'profile']
|
|
17
|
+
* @see https://apis.roblox.com/oauth/.well-known/openid-configuration
|
|
18
|
+
* @example ['openid', 'profile', 'asset:read', 'universe-messaging-service:publish']
|
|
19
|
+
*/
|
|
20
|
+
scope?: string[];
|
|
21
|
+
/**
|
|
22
|
+
* Roblox OAuth Authorization URL
|
|
23
|
+
* @default 'https://apis.roblox.com/oauth/v1/authorize'
|
|
24
|
+
*/
|
|
25
|
+
authorizationURL?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Roblox OAuth Token URL
|
|
28
|
+
* @default 'https://apis.roblox.com/oauth/v1/token'
|
|
29
|
+
*/
|
|
30
|
+
tokenURL?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Extra authorization parameters to provide to the authorization URL
|
|
33
|
+
* @see https://create.roblox.com/docs/cloud/auth/oauth2-reference#get-v1authorize
|
|
34
|
+
*/
|
|
35
|
+
authorizationParams?: Record<string, string>;
|
|
36
|
+
/**
|
|
37
|
+
* Redirect URL to allow overriding for situations like prod failing to determine public hostname
|
|
38
|
+
* @default process.env.NUXT_OAUTH_ROBLOX_REDIRECT_URL or current URL
|
|
39
|
+
*/
|
|
40
|
+
redirectURL?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface OAuthRobloxUser {
|
|
43
|
+
/**
|
|
44
|
+
* The resource path of the user
|
|
45
|
+
* @example "users/123"
|
|
46
|
+
*/
|
|
47
|
+
path: string;
|
|
48
|
+
/**
|
|
49
|
+
* The timestamp at which the user was created
|
|
50
|
+
* @readonly
|
|
51
|
+
* @example "2023-07-05T12:34:56Z"
|
|
52
|
+
*/
|
|
53
|
+
createTime: string;
|
|
54
|
+
/**
|
|
55
|
+
* Unique ID that identifies a user in Roblox
|
|
56
|
+
* @readonly
|
|
57
|
+
* @example "123456"
|
|
58
|
+
*/
|
|
59
|
+
id: string;
|
|
60
|
+
/**
|
|
61
|
+
* Unique username for a user in Roblox
|
|
62
|
+
* @example "exampleUser"
|
|
63
|
+
*/
|
|
64
|
+
name: string;
|
|
65
|
+
/**
|
|
66
|
+
* Display name for the user
|
|
67
|
+
* @example "userDefinedName"
|
|
68
|
+
*/
|
|
69
|
+
displayName: string;
|
|
70
|
+
/**
|
|
71
|
+
* User-defined information about themselves
|
|
72
|
+
* @example "Example User's bio"
|
|
73
|
+
*/
|
|
74
|
+
about: string;
|
|
75
|
+
/**
|
|
76
|
+
* Current locale selected by the user as an IETF language code
|
|
77
|
+
* @example "en-US"
|
|
78
|
+
*/
|
|
79
|
+
locale: string;
|
|
80
|
+
/**
|
|
81
|
+
* Whether the user is a premium user
|
|
82
|
+
* @readonly
|
|
83
|
+
* @example true
|
|
84
|
+
*/
|
|
85
|
+
premium: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Specifies if the user is identity-verified
|
|
88
|
+
* Verification includes, but isn't limited to, non-VoIP phone numbers or government IDs
|
|
89
|
+
* Available only with the user.advanced:read scope
|
|
90
|
+
* @readonly
|
|
91
|
+
* @example true
|
|
92
|
+
*/
|
|
93
|
+
idVerified: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* User's social network profiles and visibility.
|
|
96
|
+
*/
|
|
97
|
+
socialNetworkProfiles: {
|
|
98
|
+
/**
|
|
99
|
+
* Facebook profile URI.
|
|
100
|
+
*/
|
|
101
|
+
facebook?: string;
|
|
102
|
+
/**
|
|
103
|
+
* Twitter profile URI.
|
|
104
|
+
*/
|
|
105
|
+
twitter?: string;
|
|
106
|
+
/**
|
|
107
|
+
* YouTube profile URI.
|
|
108
|
+
*/
|
|
109
|
+
youtube?: string;
|
|
110
|
+
/**
|
|
111
|
+
* Twitch profile URI.
|
|
112
|
+
*/
|
|
113
|
+
twitch?: string;
|
|
114
|
+
/**
|
|
115
|
+
* Guilded profile URI.
|
|
116
|
+
*/
|
|
117
|
+
guilded?: string;
|
|
118
|
+
/**
|
|
119
|
+
* Visibility of the social network profiles.
|
|
120
|
+
* Available only with the user.social:read scope
|
|
121
|
+
* @example "SOCIAL_NETWORK_VISIBILITY_UNSPECIFIED"
|
|
122
|
+
*/
|
|
123
|
+
visibility: 'SOCIAL_NETWORK_VISIBILITY_UNSPECIFIED' | 'NO_ONE' | 'FRIENDS' | 'FRIENDS_AND_FOLLOWING' | 'FRIENDS_FOLLOWING_AND_FOLLOWERS' | 'EVERYONE';
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
export declare function defineOAuthRobloxEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthRobloxConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { eventHandler, getQuery, sendRedirect } 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 defineOAuthRobloxEventHandler({ config, onSuccess, onError }) {
|
|
7
|
+
return eventHandler(async (event) => {
|
|
8
|
+
config = defu(config, useRuntimeConfig(event).oauth?.roblox, {
|
|
9
|
+
authorizationURL: "https://apis.roblox.com/oauth/v1/authorize",
|
|
10
|
+
tokenURL: "https://apis.roblox.com/oauth/v1/token",
|
|
11
|
+
authorizationParams: {}
|
|
12
|
+
});
|
|
13
|
+
const query = getQuery(event);
|
|
14
|
+
if (!config.clientId || !config.clientSecret) {
|
|
15
|
+
return handleMissingConfiguration(event, "roblox", ["clientId", "clientSecret"], onError);
|
|
16
|
+
}
|
|
17
|
+
if (query.error) {
|
|
18
|
+
return handleAccessTokenErrorResponse(event, "roblox", query, onError);
|
|
19
|
+
}
|
|
20
|
+
const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
|
|
21
|
+
if (!query.code) {
|
|
22
|
+
config.scope = config.scope || [];
|
|
23
|
+
return sendRedirect(
|
|
24
|
+
event,
|
|
25
|
+
withQuery(config.authorizationURL, {
|
|
26
|
+
response_type: "code",
|
|
27
|
+
client_id: config.clientId,
|
|
28
|
+
redirect_uri: redirectURL,
|
|
29
|
+
scope: config.scope.join(" "),
|
|
30
|
+
...config.authorizationParams
|
|
31
|
+
})
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const tokens = await requestAccessToken(config.tokenURL, {
|
|
35
|
+
body: {
|
|
36
|
+
client_id: config.clientId,
|
|
37
|
+
client_secret: config.clientSecret,
|
|
38
|
+
grant_type: "authorization_code",
|
|
39
|
+
redirect_uri: redirectURL,
|
|
40
|
+
code: query.code
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
if (tokens.error) {
|
|
44
|
+
return handleAccessTokenErrorResponse(event, "roblox", tokens, onError);
|
|
45
|
+
}
|
|
46
|
+
const accessToken = tokens.access_token;
|
|
47
|
+
const userInfo = await $fetch("https://apis.roblox.com/oauth/userinfo", {
|
|
48
|
+
headers: {
|
|
49
|
+
"user-agent": "Nuxt Auth Utils",
|
|
50
|
+
"Authorization": `Bearer ${accessToken}`
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
const user = await $fetch(`https://apis.roblox.com/cloud/v2/users/${userInfo.sub}`, {
|
|
54
|
+
headers: {
|
|
55
|
+
"user-agent": "Nuxt Auth Utils",
|
|
56
|
+
"Authorization": `Bearer ${accessToken}`
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return onSuccess(event, {
|
|
60
|
+
tokens,
|
|
61
|
+
user
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { eventHandler, getQuery, sendRedirect } from "h3";
|
|
2
2
|
import { withQuery } from "ufo";
|
|
3
3
|
import { defu } from "defu";
|
|
4
|
-
import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken } from "../utils.js";
|
|
4
|
+
import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken, handleState, handlePkceVerifier, handleInvalidState } from "../utils.js";
|
|
5
5
|
import { useRuntimeConfig, createError } from "#imports";
|
|
6
6
|
export function defineOAuthZitadelEventHandler({ config, onSuccess, onError }) {
|
|
7
7
|
return eventHandler(async (event) => {
|
|
@@ -18,12 +18,14 @@ export function defineOAuthZitadelEventHandler({ config, onSuccess, onError }) {
|
|
|
18
18
|
if (!onError) throw error;
|
|
19
19
|
return onError(event, error);
|
|
20
20
|
}
|
|
21
|
-
if (!config.clientId || !config.
|
|
22
|
-
return handleMissingConfiguration(event, "zitadel", ["clientId", "
|
|
21
|
+
if (!config.clientId || !config.domain) {
|
|
22
|
+
return handleMissingConfiguration(event, "zitadel", ["clientId", "domain"], onError);
|
|
23
23
|
}
|
|
24
24
|
const authorizationURL = `https://${config.domain}/oauth/v2/authorize`;
|
|
25
25
|
const tokenURL = `https://${config.domain}/oauth/v2/token`;
|
|
26
26
|
const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
|
|
27
|
+
const verifier = await handlePkceVerifier(event);
|
|
28
|
+
const state = await handleState(event);
|
|
27
29
|
if (!query.code) {
|
|
28
30
|
config.scope = config.scope || ["openid"];
|
|
29
31
|
return sendRedirect(
|
|
@@ -33,22 +35,33 @@ export function defineOAuthZitadelEventHandler({ config, onSuccess, onError }) {
|
|
|
33
35
|
client_id: config.clientId,
|
|
34
36
|
redirect_uri: redirectURL,
|
|
35
37
|
scope: config.scope.join(" "),
|
|
38
|
+
state,
|
|
39
|
+
code_challenge: verifier.code_challenge,
|
|
40
|
+
code_challenge_method: verifier.code_challenge_method,
|
|
36
41
|
...config.authorizationParams
|
|
37
42
|
})
|
|
38
43
|
);
|
|
39
44
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
},
|
|
45
|
+
if (query.state !== state) {
|
|
46
|
+
return handleInvalidState(event, "zitadel", onError);
|
|
47
|
+
}
|
|
48
|
+
const request = {
|
|
45
49
|
body: {
|
|
46
50
|
grant_type: "authorization_code",
|
|
47
51
|
client_id: config.clientId,
|
|
48
52
|
redirect_uri: redirectURL,
|
|
49
|
-
code: query.code
|
|
53
|
+
code: query.code,
|
|
54
|
+
code_verifier: verifier.code_verifier
|
|
50
55
|
}
|
|
51
|
-
}
|
|
56
|
+
};
|
|
57
|
+
if (config.clientSecret) {
|
|
58
|
+
const basicAuthorization = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString("base64");
|
|
59
|
+
request.headers = {
|
|
60
|
+
"Authorization": `Basic ${basicAuthorization}`,
|
|
61
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const tokens = await requestAccessToken(tokenURL, request);
|
|
52
65
|
if (tokens.error) {
|
|
53
66
|
return handleAccessTokenErrorResponse(event, "zitadel", tokens, onError);
|
|
54
67
|
}
|
|
@@ -14,7 +14,7 @@ export interface RequestAccessTokenBody {
|
|
|
14
14
|
client_secret?: string;
|
|
15
15
|
[key: string]: string | undefined;
|
|
16
16
|
}
|
|
17
|
-
interface RequestAccessTokenOptions {
|
|
17
|
+
export interface RequestAccessTokenOptions {
|
|
18
18
|
body?: RequestAccessTokenBody;
|
|
19
19
|
params?: Record<string, string | undefined>;
|
|
20
20
|
headers?: Record<string, string>;
|
|
@@ -49,6 +49,9 @@ function _useSession(event, config = {}) {
|
|
|
49
49
|
const runtimeConfig = useRuntimeConfig(isEvent(event) ? event : void 0);
|
|
50
50
|
const envSessionPassword = `${runtimeConfig.nitro?.envPrefix || "NUXT_"}SESSION_PASSWORD`;
|
|
51
51
|
sessionConfig = defu({ password: process.env[envSessionPassword] }, runtimeConfig.session);
|
|
52
|
+
if (!sessionConfig.password) {
|
|
53
|
+
console.error(`[nuxt-auth-utils] ${envSessionPassword} environment variable or runtimeConfig.session.password was not set.`);
|
|
54
|
+
}
|
|
52
55
|
}
|
|
53
56
|
const finalConfig = defu(config, sessionConfig);
|
|
54
57
|
return useSession(event, finalConfig);
|
|
@@ -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' | (string & {});
|
|
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' | (string & {});
|
|
4
4
|
export type OnError = (event: H3Event, error: H3Error) => Promise<void> | void;
|
|
5
5
|
export interface OAuthConfig<TConfig, TResult = {
|
|
6
6
|
user: any;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-auth-utils",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.23",
|
|
4
4
|
"description": "Add Authentication to Nuxt applications with secured & sealed cookies sessions.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -35,15 +35,15 @@
|
|
|
35
35
|
"test:watch": "vitest watch"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@adonisjs/hash": "^9.
|
|
39
|
-
"@nuxt/kit": "^3.
|
|
38
|
+
"@adonisjs/hash": "^9.1.1",
|
|
39
|
+
"@nuxt/kit": "^3.18.1",
|
|
40
40
|
"defu": "^6.1.4",
|
|
41
|
-
"h3": "^1.
|
|
41
|
+
"h3": "^1.15.4",
|
|
42
42
|
"hookable": "^5.5.3",
|
|
43
|
-
"jose": "^5.
|
|
43
|
+
"jose": "^5.10.0",
|
|
44
44
|
"ofetch": "^1.4.1",
|
|
45
|
-
"openid-client": "^6.
|
|
46
|
-
"pathe": "^2.0.
|
|
45
|
+
"openid-client": "^6.6.2",
|
|
46
|
+
"pathe": "^2.0.3",
|
|
47
47
|
"scule": "^1.3.0",
|
|
48
48
|
"uncrypto": "^0.1.3"
|
|
49
49
|
},
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
|
-
"@iconify-json/simple-icons": "^1.2.
|
|
71
|
+
"@iconify-json/simple-icons": "^1.2.46",
|
|
72
72
|
"@nuxt/devtools": "2.0.0-beta.7",
|
|
73
73
|
"@nuxt/eslint-config": "^0.7.6",
|
|
74
74
|
"@nuxt/module-builder": "^0.8.4",
|
|
@@ -78,10 +78,10 @@
|
|
|
78
78
|
"@nuxt/ui-pro": "^1.7.0",
|
|
79
79
|
"@simplewebauthn/types": "^11.0.0",
|
|
80
80
|
"changelogen": "^0.5.7",
|
|
81
|
-
"eslint": "^9.
|
|
82
|
-
"nuxt": "^3.
|
|
81
|
+
"eslint": "^9.32.0",
|
|
82
|
+
"nuxt": "^3.18.1",
|
|
83
83
|
"typescript": "5.6.3",
|
|
84
84
|
"vitest": "^3.0.5",
|
|
85
|
-
"vue-tsc": "^2.2.
|
|
85
|
+
"vue-tsc": "^2.2.12"
|
|
86
86
|
}
|
|
87
87
|
}
|