nuxt-auth-utils 0.5.31 → 0.5.33
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 +55 -4
- package/dist/module.json +1 -1
- package/dist/module.mjs +19 -1
- package/dist/runtime/app/composables/session.js +8 -3
- package/dist/runtime/app/plugins/session.client.js +8 -4
- package/dist/runtime/server/lib/oauth/apple.js +4 -1
- package/dist/runtime/server/lib/oauth/atlassian.js +1 -1
- package/dist/runtime/server/lib/oauth/auth0.js +1 -1
- package/dist/runtime/server/lib/oauth/authentik.js +1 -1
- package/dist/runtime/server/lib/oauth/azureb2c.js +1 -1
- package/dist/runtime/server/lib/oauth/battledotnet.js +1 -1
- package/dist/runtime/server/lib/oauth/box.js +1 -1
- package/dist/runtime/server/lib/oauth/cloudflare.d.ts +86 -0
- package/dist/runtime/server/lib/oauth/cloudflare.js +72 -0
- package/dist/runtime/server/lib/oauth/cognito.js +1 -1
- package/dist/runtime/server/lib/oauth/discord.js +1 -1
- package/dist/runtime/server/lib/oauth/dropbox.js +1 -1
- package/dist/runtime/server/lib/oauth/facebook.d.ts +13 -2
- package/dist/runtime/server/lib/oauth/facebook.js +16 -4
- package/dist/runtime/server/lib/oauth/gitea.js +1 -1
- package/dist/runtime/server/lib/oauth/github.js +1 -1
- package/dist/runtime/server/lib/oauth/gitlab.js +1 -1
- package/dist/runtime/server/lib/oauth/google.js +1 -1
- package/dist/runtime/server/lib/oauth/heroku.js +1 -1
- package/dist/runtime/server/lib/oauth/hubspot.js +1 -1
- package/dist/runtime/server/lib/oauth/instagram.js +1 -1
- package/dist/runtime/server/lib/oauth/keycloak.js +1 -1
- package/dist/runtime/server/lib/oauth/kick.js +1 -1
- package/dist/runtime/server/lib/oauth/line.js +1 -1
- package/dist/runtime/server/lib/oauth/linear.js +1 -1
- package/dist/runtime/server/lib/oauth/linkedin.js +1 -1
- package/dist/runtime/server/lib/oauth/livechat.js +1 -1
- package/dist/runtime/server/lib/oauth/mastodon.d.ts +62 -0
- package/dist/runtime/server/lib/oauth/mastodon.js +178 -0
- package/dist/runtime/server/lib/oauth/microsoft.js +1 -1
- package/dist/runtime/server/lib/oauth/oidc.js +5 -4
- package/dist/runtime/server/lib/oauth/okta.js +1 -1
- package/dist/runtime/server/lib/oauth/ory.js +1 -1
- package/dist/runtime/server/lib/oauth/osu.js +1 -1
- package/dist/runtime/server/lib/oauth/paypal.js +1 -1
- package/dist/runtime/server/lib/oauth/polar.js +1 -1
- package/dist/runtime/server/lib/oauth/riotgames.js +1 -1
- package/dist/runtime/server/lib/oauth/roblox.js +1 -1
- package/dist/runtime/server/lib/oauth/salesforce.d.ts +8 -3
- package/dist/runtime/server/lib/oauth/salesforce.js +9 -5
- package/dist/runtime/server/lib/oauth/seznam.js +1 -1
- package/dist/runtime/server/lib/oauth/shopifyCustomer.js +2 -2
- package/dist/runtime/server/lib/oauth/slack.js +1 -1
- package/dist/runtime/server/lib/oauth/spotify.js +1 -1
- package/dist/runtime/server/lib/oauth/steam.js +1 -1
- package/dist/runtime/server/lib/oauth/strava.js +1 -1
- package/dist/runtime/server/lib/oauth/tiktok.js +1 -1
- package/dist/runtime/server/lib/oauth/twitch.js +1 -1
- package/dist/runtime/server/lib/oauth/vk.js +1 -1
- package/dist/runtime/server/lib/oauth/wechat.d.ts +67 -0
- package/dist/runtime/server/lib/oauth/wechat.js +107 -0
- package/dist/runtime/server/lib/oauth/workos.js +1 -1
- package/dist/runtime/server/lib/oauth/x.js +1 -1
- package/dist/runtime/server/lib/oauth/xsuaa.js +1 -1
- package/dist/runtime/server/lib/oauth/yandex.js +1 -1
- package/dist/runtime/server/lib/oauth/zitadel.js +1 -1
- package/dist/runtime/server/lib/utils.d.ts +1 -0
- package/dist/runtime/server/lib/utils.js +3 -3
- package/dist/runtime/server/utils/session.js +6 -1
- package/dist/runtime/types/oauth-config.d.ts +1 -1
- package/package.json +12 -16
|
@@ -33,7 +33,7 @@ export function defineOAuthKickEventHandler({ config, onSuccess, onError }) {
|
|
|
33
33
|
})
|
|
34
34
|
);
|
|
35
35
|
}
|
|
36
|
-
if (query.state !== state) {
|
|
36
|
+
if (!state || query.state !== state) {
|
|
37
37
|
return handleInvalidState(event, "kick", onError);
|
|
38
38
|
}
|
|
39
39
|
const tokens = await requestAccessToken(config.tokenURL, {
|
|
@@ -39,7 +39,7 @@ export function defineOAuthLinearEventHandler({ config, onSuccess, onError }) {
|
|
|
39
39
|
})
|
|
40
40
|
);
|
|
41
41
|
}
|
|
42
|
-
if (query.state !== state) {
|
|
42
|
+
if (!state || query.state !== state) {
|
|
43
43
|
return handleInvalidState(event, "linear", onError);
|
|
44
44
|
}
|
|
45
45
|
const tokens = await requestAccessToken(config.tokenURL, {
|
|
@@ -36,7 +36,7 @@ export function defineOAuthLinkedInEventHandler({ config, onSuccess, onError })
|
|
|
36
36
|
})
|
|
37
37
|
);
|
|
38
38
|
}
|
|
39
|
-
if (query.state !== state) {
|
|
39
|
+
if (!state || query.state !== state) {
|
|
40
40
|
return handleInvalidState(event, "linkedin", onError);
|
|
41
41
|
}
|
|
42
42
|
const tokens = await requestAccessToken(config.tokenURL, {
|
|
@@ -46,7 +46,7 @@ export function defineOAuthLiveChatEventHandler({
|
|
|
46
46
|
})
|
|
47
47
|
);
|
|
48
48
|
}
|
|
49
|
-
if (query.state !== state) {
|
|
49
|
+
if (!state || query.state !== state) {
|
|
50
50
|
return handleInvalidState(event, "livechat", onError);
|
|
51
51
|
}
|
|
52
52
|
const tokens = await requestAccessToken(config.tokenURL, {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { OAuthConfig } from '#auth-utils';
|
|
2
|
+
export interface OAuthMastodonConfig {
|
|
3
|
+
/**
|
|
4
|
+
* The instance domain to authenticate against, e.g. `mastodon.social`.
|
|
5
|
+
*
|
|
6
|
+
* Unlike most OAuth providers, a Mastodon account lives on a single instance and there is no
|
|
7
|
+
* shared authorization server, so the instance must be known before the flow can start. If not
|
|
8
|
+
* set here, it can be provided at runtime as an `instance` query parameter on the initial request
|
|
9
|
+
* (e.g. `/auth/mastodon?instance=mastodon.social`). A full handle such as `@user@mastodon.social`
|
|
10
|
+
* or `user@mastodon.social` is also accepted, only the domain part is used.
|
|
11
|
+
* @default process.env.NUXT_OAUTH_MASTODON_INSTANCE
|
|
12
|
+
* @example 'mastodon.social'
|
|
13
|
+
*/
|
|
14
|
+
instance?: string;
|
|
15
|
+
/**
|
|
16
|
+
* The name of the application shown to users on the instance's authorization screen.
|
|
17
|
+
*
|
|
18
|
+
* Mastodon has no concept of a pre-registered, shared client id: an application must be
|
|
19
|
+
* registered against every instance it authenticates users on (`POST /api/v1/apps`). This is
|
|
20
|
+
* that application's display name.
|
|
21
|
+
* @default 'Nuxt Auth Utils'
|
|
22
|
+
*/
|
|
23
|
+
clientName?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Mastodon OAuth Scope.
|
|
26
|
+
* @default ['read']
|
|
27
|
+
* @see https://docs.joinmastodon.org/api/oauth-scopes/
|
|
28
|
+
* @example ['read', 'read:accounts']
|
|
29
|
+
*/
|
|
30
|
+
scope?: string[];
|
|
31
|
+
/**
|
|
32
|
+
* Redirect URL to allow overriding for situations like prod failing to determine public hostname
|
|
33
|
+
* @default process.env.NUXT_OAUTH_MASTODON_REDIRECT_URL or current URL
|
|
34
|
+
*/
|
|
35
|
+
redirectURL?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface OAuthMastodonUser {
|
|
38
|
+
id: string;
|
|
39
|
+
username: string;
|
|
40
|
+
/**
|
|
41
|
+
* Always the full `user@instance` handle here, even for the authenticated user's own account.
|
|
42
|
+
* Mastodon's API normally only qualifies `acct` with `@instance` for remote accounts and returns
|
|
43
|
+
* the bare username for your own, we normalize it since an account only means something together
|
|
44
|
+
* with the instance it lives on.
|
|
45
|
+
*/
|
|
46
|
+
acct: string;
|
|
47
|
+
display_name: string;
|
|
48
|
+
url: string;
|
|
49
|
+
avatar: string;
|
|
50
|
+
[key: string]: unknown;
|
|
51
|
+
}
|
|
52
|
+
interface MastodonTokens {
|
|
53
|
+
access_token: string;
|
|
54
|
+
token_type: string;
|
|
55
|
+
scope: string;
|
|
56
|
+
created_at: number;
|
|
57
|
+
}
|
|
58
|
+
export declare function defineOAuthMastodonEventHandler<TUser = OAuthMastodonUser>({ config: userConfig, onSuccess, onError }: OAuthConfig<OAuthMastodonConfig, {
|
|
59
|
+
user: TUser;
|
|
60
|
+
tokens: MastodonTokens;
|
|
61
|
+
}>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
|
|
62
|
+
export {};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { createError, deleteCookie, eventHandler, getCookie, getQuery, sendRedirect, setCookie } from "h3";
|
|
2
|
+
import { withQuery } from "ufo";
|
|
3
|
+
import { defu } from "defu";
|
|
4
|
+
import { $fetch } from "ofetch";
|
|
5
|
+
import { getOAuthRedirectURL, handleAccessTokenErrorResponse, handleInvalidState, handleState, isDevelopment, requestAccessToken } from "../utils.js";
|
|
6
|
+
import { useRuntimeConfig } from "#imports";
|
|
7
|
+
const FLOW_COOKIE_NAME = "nuxt-auth-mastodon-flow";
|
|
8
|
+
const appRegistry = /* @__PURE__ */ new Map();
|
|
9
|
+
const pendingRegistrations = /* @__PURE__ */ new Map();
|
|
10
|
+
const MAX_REGISTERED_INSTANCES = 1e3;
|
|
11
|
+
function appRegistryKey(instance, redirectURL) {
|
|
12
|
+
return `${instance}::${redirectURL}`;
|
|
13
|
+
}
|
|
14
|
+
function resolveInstanceDomain(input) {
|
|
15
|
+
const withoutProtocol = input.replace(/^https?:\/\//, "").replace(/\/.*$/, "");
|
|
16
|
+
const withoutLeadingAt = withoutProtocol.replace(/^@/, "");
|
|
17
|
+
const domain = withoutLeadingAt.includes("@") ? withoutLeadingAt.split("@").pop() : withoutLeadingAt;
|
|
18
|
+
try {
|
|
19
|
+
return new URL(`https://${domain}`).hostname;
|
|
20
|
+
} catch {
|
|
21
|
+
return domain;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function isPubliclyAddressableInstance(instance) {
|
|
25
|
+
if (!instance || /\s/.test(instance) || instance.endsWith(".")) return false;
|
|
26
|
+
try {
|
|
27
|
+
if (new URL(`https://${instance}`).hostname !== instance) return false;
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
if (instance === "localhost" || instance.endsWith(".localhost") || instance.endsWith(".local")) return false;
|
|
32
|
+
if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(instance)) return false;
|
|
33
|
+
if (instance.includes(":")) return false;
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
async function registerApp(instance, redirectURL, scope, clientName) {
|
|
37
|
+
const app = await $fetch(`https://${instance}/api/v1/apps`, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
body: {
|
|
40
|
+
client_name: clientName,
|
|
41
|
+
redirect_uris: redirectURL,
|
|
42
|
+
scopes: scope
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return { client_id: app.client_id, client_secret: app.client_secret };
|
|
46
|
+
}
|
|
47
|
+
async function getOrRegisterApp(instance, redirectURL, scope, clientName) {
|
|
48
|
+
const key = appRegistryKey(instance, redirectURL);
|
|
49
|
+
const cached = appRegistry.get(key);
|
|
50
|
+
if (cached) return cached;
|
|
51
|
+
const pending = pendingRegistrations.get(key);
|
|
52
|
+
if (pending) return pending;
|
|
53
|
+
const registration = registerApp(instance, redirectURL, scope, clientName).then((app) => {
|
|
54
|
+
if (appRegistry.size >= MAX_REGISTERED_INSTANCES && !appRegistry.has(key)) {
|
|
55
|
+
const oldestKey = appRegistry.keys().next().value;
|
|
56
|
+
if (oldestKey) appRegistry.delete(oldestKey);
|
|
57
|
+
}
|
|
58
|
+
appRegistry.set(key, app);
|
|
59
|
+
return app;
|
|
60
|
+
}).finally(() => {
|
|
61
|
+
pendingRegistrations.delete(key);
|
|
62
|
+
});
|
|
63
|
+
pendingRegistrations.set(key, registration);
|
|
64
|
+
return registration;
|
|
65
|
+
}
|
|
66
|
+
export function defineOAuthMastodonEventHandler({ config: userConfig, onSuccess, onError }) {
|
|
67
|
+
return eventHandler(async (event) => {
|
|
68
|
+
const config = defu({}, userConfig, useRuntimeConfig(event).oauth?.mastodon, {
|
|
69
|
+
clientName: "Nuxt Auth Utils"
|
|
70
|
+
});
|
|
71
|
+
config.scope ||= ["read"];
|
|
72
|
+
const query = getQuery(event);
|
|
73
|
+
if (query.error) {
|
|
74
|
+
const error = createError({
|
|
75
|
+
statusCode: 401,
|
|
76
|
+
message: `Mastodon login failed: ${query.error || "Unknown error"}`,
|
|
77
|
+
data: query
|
|
78
|
+
});
|
|
79
|
+
if (!onError) throw error;
|
|
80
|
+
return onError(event, error);
|
|
81
|
+
}
|
|
82
|
+
const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
|
|
83
|
+
const state = await handleState(event);
|
|
84
|
+
const scope = (config.scope || ["read"]).join(" ");
|
|
85
|
+
if (!query.code) {
|
|
86
|
+
const rawInstance = query.instance?.toString() || config.instance;
|
|
87
|
+
if (!rawInstance) {
|
|
88
|
+
const error = createError({
|
|
89
|
+
statusCode: 400,
|
|
90
|
+
message: "Mastodon login failed: missing instance. Provide it via the `instance` query parameter (e.g. `/auth/mastodon?instance=mastodon.social`), a full handle (`@user@mastodon.social`), or the `instance` config/`NUXT_OAUTH_MASTODON_INSTANCE` env variable."
|
|
91
|
+
});
|
|
92
|
+
if (!onError) throw error;
|
|
93
|
+
return onError(event, error);
|
|
94
|
+
}
|
|
95
|
+
const instance2 = resolveInstanceDomain(rawInstance);
|
|
96
|
+
if (!isPubliclyAddressableInstance(instance2)) {
|
|
97
|
+
const error = createError({
|
|
98
|
+
statusCode: 400,
|
|
99
|
+
message: `Mastodon login failed: "${instance2}" is not a valid instance domain.`
|
|
100
|
+
});
|
|
101
|
+
if (!onError) throw error;
|
|
102
|
+
return onError(event, error);
|
|
103
|
+
}
|
|
104
|
+
let app;
|
|
105
|
+
try {
|
|
106
|
+
app = await getOrRegisterApp(instance2, redirectURL, scope, config.clientName || "Nuxt Auth Utils");
|
|
107
|
+
} catch {
|
|
108
|
+
const error = createError({
|
|
109
|
+
statusCode: 500,
|
|
110
|
+
message: `Mastodon login failed: could not register the application with instance "${instance2}". Make sure it is a reachable Mastodon (or compatible) server.`
|
|
111
|
+
});
|
|
112
|
+
if (!onError) throw error;
|
|
113
|
+
return onError(event, error);
|
|
114
|
+
}
|
|
115
|
+
const flowState2 = { instance: instance2, clientId: app.client_id, clientSecret: app.client_secret };
|
|
116
|
+
setCookie(event, FLOW_COOKIE_NAME, btoa(JSON.stringify(flowState2)), {
|
|
117
|
+
httpOnly: true,
|
|
118
|
+
secure: !isDevelopment,
|
|
119
|
+
sameSite: "lax",
|
|
120
|
+
maxAge: 60 * 10,
|
|
121
|
+
path: "/"
|
|
122
|
+
});
|
|
123
|
+
return sendRedirect(event, withQuery(`https://${instance2}/oauth/authorize`, {
|
|
124
|
+
client_id: app.client_id,
|
|
125
|
+
redirect_uri: redirectURL,
|
|
126
|
+
response_type: "code",
|
|
127
|
+
scope,
|
|
128
|
+
state
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
if (!state || query.state !== state) {
|
|
132
|
+
return handleInvalidState(event, "mastodon", onError);
|
|
133
|
+
}
|
|
134
|
+
const flowCookie = getCookie(event, FLOW_COOKIE_NAME);
|
|
135
|
+
deleteCookie(event, FLOW_COOKIE_NAME, { path: "/" });
|
|
136
|
+
let flowState;
|
|
137
|
+
try {
|
|
138
|
+
flowState = flowCookie ? JSON.parse(atob(flowCookie)) : void 0;
|
|
139
|
+
} catch {
|
|
140
|
+
flowState = void 0;
|
|
141
|
+
}
|
|
142
|
+
if (!flowState || !isPubliclyAddressableInstance(flowState.instance)) {
|
|
143
|
+
const error = createError({
|
|
144
|
+
statusCode: 400,
|
|
145
|
+
message: "Mastodon login failed: could not recover the application credentials for this login (missing or invalid flow cookie, the flow may have taken too long or cookies are blocked)."
|
|
146
|
+
});
|
|
147
|
+
if (!onError) throw error;
|
|
148
|
+
return onError(event, error);
|
|
149
|
+
}
|
|
150
|
+
const { instance, clientId, clientSecret } = flowState;
|
|
151
|
+
const tokens = await requestAccessToken(`https://${instance}/oauth/token`, {
|
|
152
|
+
body: {
|
|
153
|
+
grant_type: "authorization_code",
|
|
154
|
+
client_id: clientId,
|
|
155
|
+
client_secret: clientSecret,
|
|
156
|
+
redirect_uri: redirectURL,
|
|
157
|
+
code: query.code,
|
|
158
|
+
scope
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
if (tokens.error) {
|
|
162
|
+
return handleAccessTokenErrorResponse(event, "mastodon", tokens, onError);
|
|
163
|
+
}
|
|
164
|
+
const account = await $fetch(`https://${instance}/api/v1/accounts/verify_credentials`, {
|
|
165
|
+
headers: {
|
|
166
|
+
Authorization: `Bearer ${tokens.access_token}`
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
const user = {
|
|
170
|
+
...account,
|
|
171
|
+
acct: account.acct.includes("@") ? account.acct : `${account.acct}@${instance}`
|
|
172
|
+
};
|
|
173
|
+
return onSuccess(event, {
|
|
174
|
+
user,
|
|
175
|
+
tokens
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
}
|
|
@@ -31,7 +31,7 @@ export function defineOAuthMicrosoftEventHandler({ config, onSuccess, onError })
|
|
|
31
31
|
})
|
|
32
32
|
);
|
|
33
33
|
}
|
|
34
|
-
if (query.state !== state) {
|
|
34
|
+
if (!state || query.state !== state) {
|
|
35
35
|
return handleInvalidState(event, "microsoft", onError);
|
|
36
36
|
}
|
|
37
37
|
const tokens = await requestAccessToken(tokenURL, {
|
|
@@ -5,9 +5,7 @@ import { withQuery } from "ufo";
|
|
|
5
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
|
-
config = defu(config, useRuntimeConfig(event).oauth.oidc
|
|
9
|
-
scope: ["openid"]
|
|
10
|
-
});
|
|
8
|
+
config = defu(config, useRuntimeConfig(event).oauth.oidc);
|
|
11
9
|
const query = getQuery(event);
|
|
12
10
|
if (query.error) {
|
|
13
11
|
const error = createError({
|
|
@@ -28,6 +26,9 @@ export function defineOAuthOidcEventHandler({ config, onSuccess, onError }) {
|
|
|
28
26
|
const nonce = handleNonce(event);
|
|
29
27
|
if (!query.code) {
|
|
30
28
|
config.scope = config.scope || [];
|
|
29
|
+
if (!config.scope.includes("openid")) {
|
|
30
|
+
config.scope.push("openid");
|
|
31
|
+
}
|
|
31
32
|
const authQuery = {
|
|
32
33
|
client_id: config.clientId,
|
|
33
34
|
redirect_uri: redirectURL,
|
|
@@ -44,7 +45,7 @@ export function defineOAuthOidcEventHandler({ config, onSuccess, onError }) {
|
|
|
44
45
|
withQuery(oidcConfig.authorization_endpoint, authQuery)
|
|
45
46
|
);
|
|
46
47
|
}
|
|
47
|
-
if (query.state !== state) {
|
|
48
|
+
if (!state || query.state !== state) {
|
|
48
49
|
return handleInvalidState(event, "oidc", onError);
|
|
49
50
|
}
|
|
50
51
|
const tokenQuery = {
|
|
@@ -113,7 +113,7 @@ export function defineOAuthOktaEventHandler({ config, onSuccess, onError }) {
|
|
|
113
113
|
})
|
|
114
114
|
);
|
|
115
115
|
}
|
|
116
|
-
if (query.state !== state) {
|
|
116
|
+
if (!state || query.state !== state) {
|
|
117
117
|
return handleInvalidState(event, "okta", onError);
|
|
118
118
|
}
|
|
119
119
|
const tokens = await requestAccessToken(tokenURL, {
|
|
@@ -56,7 +56,7 @@ export function defineOAuthOryEventHandler({ config, onSuccess, onError }) {
|
|
|
56
56
|
})
|
|
57
57
|
);
|
|
58
58
|
}
|
|
59
|
-
if (query.state !== state) {
|
|
59
|
+
if (!state || query.state !== state) {
|
|
60
60
|
return handleInvalidState(event, "ory", onError);
|
|
61
61
|
}
|
|
62
62
|
const tokenURL = `${config.sdkURL}${config.tokenURL}`;
|
|
@@ -33,7 +33,7 @@ export function defineOAuthOsuEventHandler({ config, onSuccess, onError }) {
|
|
|
33
33
|
})
|
|
34
34
|
);
|
|
35
35
|
}
|
|
36
|
-
if (query.state !== state) {
|
|
36
|
+
if (!state || query.state !== state) {
|
|
37
37
|
return handleInvalidState(event, "osu", onError);
|
|
38
38
|
}
|
|
39
39
|
const tokens = await requestAccessToken(config.tokenURL, {
|
|
@@ -44,7 +44,7 @@ export function defineOAuthPaypalEventHandler({ config, onSuccess, onError }) {
|
|
|
44
44
|
})
|
|
45
45
|
);
|
|
46
46
|
}
|
|
47
|
-
if (query.state !== state) {
|
|
47
|
+
if (!state || query.state !== state) {
|
|
48
48
|
return handleInvalidState(event, "paypal", onError);
|
|
49
49
|
}
|
|
50
50
|
const tokens = await requestAccessToken(config.tokenURL, {
|
|
@@ -33,7 +33,7 @@ export function defineOAuthPolarEventHandler({ config, onSuccess, onError }) {
|
|
|
33
33
|
})
|
|
34
34
|
);
|
|
35
35
|
}
|
|
36
|
-
if (query.state !== state) {
|
|
36
|
+
if (!state || query.state !== state) {
|
|
37
37
|
return handleInvalidState(event, "polar", onError);
|
|
38
38
|
}
|
|
39
39
|
const tokens = await requestAccessToken(config.tokenURL, {
|
|
@@ -43,7 +43,7 @@ export function defineOAuthRiotGamesEventHandler({ config, onSuccess, onError })
|
|
|
43
43
|
})
|
|
44
44
|
);
|
|
45
45
|
}
|
|
46
|
-
if (query.state !== state) {
|
|
46
|
+
if (!state || query.state !== state) {
|
|
47
47
|
return handleInvalidState(event, "riotgames", onError);
|
|
48
48
|
}
|
|
49
49
|
const tokens = await requestAccessToken(config.tokenURL, {
|
|
@@ -33,7 +33,7 @@ export function defineOAuthRobloxEventHandler({ config, onSuccess, onError }) {
|
|
|
33
33
|
})
|
|
34
34
|
);
|
|
35
35
|
}
|
|
36
|
-
if (query.state !== state) {
|
|
36
|
+
if (!state || query.state !== state) {
|
|
37
37
|
return handleInvalidState(event, "roblox", onError);
|
|
38
38
|
}
|
|
39
39
|
const tokens = await requestAccessToken(config.tokenURL, {
|
|
@@ -24,14 +24,19 @@ export interface OAuthSalesforceConfig {
|
|
|
24
24
|
baseURL?: string;
|
|
25
25
|
/**
|
|
26
26
|
* Salesforce OAuth Authorization URL
|
|
27
|
-
* @default '
|
|
27
|
+
* @default '${baseURL}/services/oauth2/authorize'
|
|
28
28
|
*/
|
|
29
29
|
authorizationURL?: string;
|
|
30
30
|
/**
|
|
31
|
-
* Salesforce OAuth
|
|
32
|
-
* @default '
|
|
31
|
+
* Salesforce OAuth Token URL
|
|
32
|
+
* @default '${baseURL}/services/oauth2/token'
|
|
33
33
|
*/
|
|
34
34
|
tokenURL?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Salesforce OAuth User URL
|
|
37
|
+
* @default '${baseURL}/services/oauth2/userinfo'
|
|
38
|
+
*/
|
|
39
|
+
userURL?: string;
|
|
35
40
|
/**
|
|
36
41
|
* Extra authorization parameters to provide to the authorization URL
|
|
37
42
|
* @default {}
|
|
@@ -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, handleState, handleInvalidState } from "../utils.js";
|
|
4
|
+
import { handleMissingConfiguration, handleAccessTokenErrorResponse, getOAuthRedirectURL, requestAccessToken, handleState, handleInvalidState, handlePkceVerifier } from "../utils.js";
|
|
5
5
|
import { useRuntimeConfig, createError } from "#imports";
|
|
6
6
|
export function defineOAuthSalesforceEventHandler({
|
|
7
7
|
config,
|
|
@@ -10,7 +10,7 @@ export function defineOAuthSalesforceEventHandler({
|
|
|
10
10
|
}) {
|
|
11
11
|
return eventHandler(async (event) => {
|
|
12
12
|
const runtimeConfig = useRuntimeConfig(event).oauth?.salesforce;
|
|
13
|
-
const baseURL = config?.baseURL || "https://login.salesforce.com";
|
|
13
|
+
const baseURL = config?.baseURL || runtimeConfig?.baseURL || "https://login.salesforce.com";
|
|
14
14
|
config = defu(config, runtimeConfig, {
|
|
15
15
|
authorizationURL: `${baseURL}/services/oauth2/authorize`,
|
|
16
16
|
tokenURL: `${baseURL}/services/oauth2/token`,
|
|
@@ -30,6 +30,7 @@ export function defineOAuthSalesforceEventHandler({
|
|
|
30
30
|
return handleMissingConfiguration(event, "salesforce", ["clientId", "clientSecret"], onError);
|
|
31
31
|
}
|
|
32
32
|
const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
|
|
33
|
+
const pkce = await handlePkceVerifier(event);
|
|
33
34
|
const state = await handleState(event);
|
|
34
35
|
if (!query.code) {
|
|
35
36
|
config.scope = config.scope || ["id"];
|
|
@@ -41,11 +42,13 @@ export function defineOAuthSalesforceEventHandler({
|
|
|
41
42
|
redirect_uri: redirectURL,
|
|
42
43
|
scope: config.scope.join(" "),
|
|
43
44
|
...config.authorizationParams,
|
|
45
|
+
code_challenge: pkce.code_challenge,
|
|
46
|
+
code_challenge_method: pkce.code_challenge_method,
|
|
44
47
|
state
|
|
45
48
|
})
|
|
46
49
|
);
|
|
47
50
|
}
|
|
48
|
-
if (query.state !== state) {
|
|
51
|
+
if (!state || query.state !== state) {
|
|
49
52
|
return handleInvalidState(event, "salesforce", onError);
|
|
50
53
|
}
|
|
51
54
|
const tokens = await requestAccessToken(config.tokenURL, {
|
|
@@ -54,14 +57,15 @@ export function defineOAuthSalesforceEventHandler({
|
|
|
54
57
|
client_id: config.clientId,
|
|
55
58
|
client_secret: config.clientSecret,
|
|
56
59
|
redirect_uri: redirectURL,
|
|
57
|
-
code: query.code
|
|
60
|
+
code: query.code,
|
|
61
|
+
code_verifier: pkce.code_verifier
|
|
58
62
|
}
|
|
59
63
|
});
|
|
60
64
|
if (tokens.error) {
|
|
61
65
|
return handleAccessTokenErrorResponse(event, "salesforce", tokens, onError);
|
|
62
66
|
}
|
|
63
67
|
const accessToken = tokens.access_token;
|
|
64
|
-
const user = await $fetch(`${baseURL}/services/oauth2/userinfo`, {
|
|
68
|
+
const user = await $fetch(config.userURL || `${baseURL}/services/oauth2/userinfo`, {
|
|
65
69
|
headers: {
|
|
66
70
|
Authorization: `Bearer ${accessToken}`
|
|
67
71
|
}
|
|
@@ -29,7 +29,7 @@ export function defineOAuthSeznamEventHandler({ config, onSuccess, onError }) {
|
|
|
29
29
|
})
|
|
30
30
|
);
|
|
31
31
|
}
|
|
32
|
-
if (query.state !== state) {
|
|
32
|
+
if (!state || query.state !== state) {
|
|
33
33
|
return handleInvalidState(event, "seznam", onError);
|
|
34
34
|
}
|
|
35
35
|
const tokens = await requestAccessToken(config.tokenURL, {
|
|
@@ -12,7 +12,7 @@ export function defineOAuthShopifyCustomerEventHandler({
|
|
|
12
12
|
config = defu(config, useRuntimeConfig(event).oauth?.shopifyCustomer, {});
|
|
13
13
|
const query = getQuery(event);
|
|
14
14
|
if (!config.clientId || !config.shopDomain) {
|
|
15
|
-
return handleMissingConfiguration(event, "
|
|
15
|
+
return handleMissingConfiguration(event, "shopifyCustomer", ["clientId", "shopDomain"], onError);
|
|
16
16
|
}
|
|
17
17
|
const verifier = await handlePkceVerifier(event);
|
|
18
18
|
const redirectURL = config.redirectURL || getOAuthRedirectURL(event);
|
|
@@ -42,7 +42,7 @@ export function defineOAuthShopifyCustomerEventHandler({
|
|
|
42
42
|
})
|
|
43
43
|
);
|
|
44
44
|
}
|
|
45
|
-
if (query.state !== state) {
|
|
45
|
+
if (!state || query.state !== state) {
|
|
46
46
|
return handleInvalidState(event, "shopifyCustomer", onError);
|
|
47
47
|
}
|
|
48
48
|
const tokens = await requestAccessToken(discoveryResponse.token_endpoint, {
|
|
@@ -33,7 +33,7 @@ export function defineOAuthSpotifyEventHandler({ config, onSuccess, onError }) {
|
|
|
33
33
|
})
|
|
34
34
|
);
|
|
35
35
|
}
|
|
36
|
-
if (query.state !== state) {
|
|
36
|
+
if (!state || query.state !== state) {
|
|
37
37
|
return handleInvalidState(event, "spotify", onError);
|
|
38
38
|
}
|
|
39
39
|
const tokens = await requestAccessToken(config.tokenURL, {
|
|
@@ -27,7 +27,7 @@ export function defineOAuthSteamEventHandler({ config, onSuccess, onError }) {
|
|
|
27
27
|
};
|
|
28
28
|
return sendRedirect(event, withQuery(config.authorizationURL, steamOpenIdParams));
|
|
29
29
|
}
|
|
30
|
-
if (query.state !== state) {
|
|
30
|
+
if (!state || query.state !== state) {
|
|
31
31
|
return handleInvalidState(event, "steam", onError);
|
|
32
32
|
}
|
|
33
33
|
if (!query["openid.signed"] || !query["openid.sig"]) {
|
|
@@ -39,7 +39,7 @@ export function defineOAuthTikTokEventHandler({ config, onSuccess, onError }) {
|
|
|
39
39
|
})
|
|
40
40
|
);
|
|
41
41
|
}
|
|
42
|
-
if (query.state !== state) {
|
|
42
|
+
if (!state || query.state !== state) {
|
|
43
43
|
return handleInvalidState(event, "tiktok", onError);
|
|
44
44
|
}
|
|
45
45
|
const tokens = await requestAccessToken(
|
|
@@ -33,7 +33,7 @@ export function defineOAuthTwitchEventHandler({ config, onSuccess, onError }) {
|
|
|
33
33
|
})
|
|
34
34
|
);
|
|
35
35
|
}
|
|
36
|
-
if (query.state !== state) {
|
|
36
|
+
if (!state || query.state !== state) {
|
|
37
37
|
return handleInvalidState(event, "twitch", onError);
|
|
38
38
|
}
|
|
39
39
|
const tokens = await requestAccessToken(config.tokenURL, {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { OAuthConfig } from '#auth-utils';
|
|
2
|
+
export interface OAuthWechatConfig {
|
|
3
|
+
/**
|
|
4
|
+
* WeChat App ID
|
|
5
|
+
* @default process.env.NUXT_OAUTH_WECHAT_APP_ID
|
|
6
|
+
*/
|
|
7
|
+
appId?: string;
|
|
8
|
+
/**
|
|
9
|
+
* WeChat App Secret
|
|
10
|
+
* @default process.env.NUXT_OAUTH_WECHAT_APP_SECRET
|
|
11
|
+
*/
|
|
12
|
+
appSecret?: string;
|
|
13
|
+
/**
|
|
14
|
+
* WeChat OAuth Scope
|
|
15
|
+
* @default ['snsapi_login']
|
|
16
|
+
* @see https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
|
|
17
|
+
*/
|
|
18
|
+
scope?: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Authorization URL
|
|
21
|
+
* @default 'https://open.weixin.qq.com/connect/qrconnect'
|
|
22
|
+
*/
|
|
23
|
+
authorizationURL?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Access token URL
|
|
26
|
+
* @default 'https://api.weixin.qq.com/sns/oauth2/access_token'
|
|
27
|
+
*/
|
|
28
|
+
tokenURL?: string;
|
|
29
|
+
/**
|
|
30
|
+
* User profile URL
|
|
31
|
+
* @default 'https://api.weixin.qq.com/sns/userinfo'
|
|
32
|
+
*/
|
|
33
|
+
userURL?: string;
|
|
34
|
+
/**
|
|
35
|
+
* User info language.
|
|
36
|
+
* @default 'zh_CN'
|
|
37
|
+
*/
|
|
38
|
+
lang?: 'zh_CN' | 'zh_TW' | 'en';
|
|
39
|
+
/**
|
|
40
|
+
* Redirect URL to allow overriding when public hostname cannot be inferred.
|
|
41
|
+
* @default process.env.NUXT_OAUTH_WECHAT_REDIRECT_URL or current URL
|
|
42
|
+
*/
|
|
43
|
+
redirectURL?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface WechatOAuthTokens {
|
|
46
|
+
access_token: string;
|
|
47
|
+
expires_in: number;
|
|
48
|
+
refresh_token: string;
|
|
49
|
+
openid: string;
|
|
50
|
+
scope: string;
|
|
51
|
+
unionid?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface WechatOAuthUser {
|
|
54
|
+
openid: string;
|
|
55
|
+
nickname: string;
|
|
56
|
+
sex: 0 | 1 | 2;
|
|
57
|
+
province: string;
|
|
58
|
+
city: string;
|
|
59
|
+
country: string;
|
|
60
|
+
headimgurl: string;
|
|
61
|
+
privilege: string[];
|
|
62
|
+
unionid?: string;
|
|
63
|
+
}
|
|
64
|
+
export declare function defineOAuthWechatEventHandler({ config, onSuccess, onError, }: OAuthConfig<OAuthWechatConfig, {
|
|
65
|
+
user: WechatOAuthUser;
|
|
66
|
+
tokens: WechatOAuthTokens;
|
|
67
|
+
}>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
|