nuxt-auther 1.0.2 → 1.0.4
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/dist/module.cjs +5 -0
- package/dist/module.d.mts +3 -0
- package/dist/module.d.ts +3 -0
- package/dist/module.json +8 -0
- package/dist/module.mjs +1007 -0
- package/dist/runtime/composables.d.ts +2 -0
- package/dist/runtime/composables.mjs +2 -0
- package/dist/runtime/core/auth.d.ts +53 -0
- package/dist/runtime/core/auth.mjs +387 -0
- package/dist/runtime/core/index.d.ts +3 -0
- package/dist/runtime/core/index.mjs +3 -0
- package/dist/runtime/core/middleware.d.ts +2 -0
- package/dist/runtime/core/middleware.mjs +41 -0
- package/dist/runtime/core/storage.d.ts +41 -0
- package/dist/runtime/core/storage.mjs +277 -0
- package/dist/runtime/inc/configuration-document-request-error.d.ts +3 -0
- package/dist/runtime/inc/configuration-document-request-error.mjs +6 -0
- package/dist/runtime/inc/configuration-document.d.ts +26 -0
- package/dist/runtime/inc/configuration-document.mjs +94 -0
- package/dist/runtime/inc/default-properties.d.ts +123 -0
- package/dist/runtime/inc/default-properties.mjs +132 -0
- package/dist/runtime/inc/expired-auth-session-error.d.ts +3 -0
- package/dist/runtime/inc/expired-auth-session-error.mjs +6 -0
- package/dist/runtime/inc/id-token.d.ts +15 -0
- package/dist/runtime/inc/id-token.mjs +81 -0
- package/dist/runtime/inc/index.d.ts +10 -0
- package/dist/runtime/inc/index.mjs +10 -0
- package/dist/runtime/inc/refresh-controller.d.ts +9 -0
- package/dist/runtime/inc/refresh-controller.mjs +29 -0
- package/dist/runtime/inc/refresh-token.d.ts +14 -0
- package/dist/runtime/inc/refresh-token.mjs +75 -0
- package/dist/runtime/inc/request-handler.d.ts +17 -0
- package/dist/runtime/inc/request-handler.mjs +93 -0
- package/dist/runtime/inc/token-status.d.ts +12 -0
- package/dist/runtime/inc/token-status.mjs +39 -0
- package/dist/runtime/inc/token.d.ts +14 -0
- package/dist/runtime/inc/token.mjs +83 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.mjs +3 -0
- package/dist/runtime/providers/auth0.d.ts +7 -0
- package/dist/runtime/providers/auth0.mjs +15 -0
- package/dist/runtime/providers/discord.d.ts +6 -0
- package/dist/runtime/providers/discord.mjs +18 -0
- package/dist/runtime/providers/facebook.d.ts +6 -0
- package/dist/runtime/providers/facebook.mjs +13 -0
- package/dist/runtime/providers/github.d.ts +6 -0
- package/dist/runtime/providers/github.mjs +15 -0
- package/dist/runtime/providers/google.d.ts +6 -0
- package/dist/runtime/providers/google.mjs +13 -0
- package/dist/runtime/providers/index.d.ts +8 -0
- package/dist/runtime/providers/index.mjs +8 -0
- package/dist/runtime/providers/laravel-jwt.d.ts +7 -0
- package/dist/runtime/providers/laravel-jwt.mjs +47 -0
- package/dist/runtime/providers/laravel-passport.d.ts +12 -0
- package/dist/runtime/providers/laravel-passport.mjs +69 -0
- package/dist/runtime/providers/laravel-sanctum.d.ts +6 -0
- package/dist/runtime/providers/laravel-sanctum.mjs +52 -0
- package/dist/runtime/schemes/auth0.d.ts +4 -0
- package/dist/runtime/schemes/auth0.mjs +13 -0
- package/dist/runtime/schemes/base.d.ts +8 -0
- package/dist/runtime/schemes/base.mjs +11 -0
- package/dist/runtime/schemes/cookie.d.ts +23 -0
- package/dist/runtime/schemes/cookie.mjs +111 -0
- package/dist/runtime/schemes/index.d.ts +8 -0
- package/dist/runtime/schemes/index.mjs +8 -0
- package/dist/runtime/schemes/laravel-jwt.d.ts +5 -0
- package/dist/runtime/schemes/laravel-jwt.mjs +6 -0
- package/dist/runtime/schemes/local.d.ts +38 -0
- package/dist/runtime/schemes/local.mjs +160 -0
- package/dist/runtime/schemes/oauth2.d.ts +61 -0
- package/dist/runtime/schemes/oauth2.mjs +374 -0
- package/dist/runtime/schemes/openIDConnect.d.ts +24 -0
- package/dist/runtime/schemes/openIDConnect.mjs +190 -0
- package/dist/runtime/schemes/refresh.d.ts +26 -0
- package/dist/runtime/schemes/refresh.mjs +141 -0
- package/dist/runtime/token-nitro.d.ts +2 -0
- package/dist/runtime/token-nitro.mjs +9 -0
- package/dist/types/index.d.ts +42 -0
- package/dist/types/openIDConnectConfigurationDocument.d.ts +31 -0
- package/dist/types/options.d.ts +125 -0
- package/dist/types/provider.d.ts +21 -0
- package/dist/types/request.d.ts +8 -0
- package/dist/types/router.d.ts +7 -0
- package/dist/types/scheme.d.ts +108 -0
- package/dist/types/store.d.ts +30 -0
- package/dist/types/strategy.d.ts +16 -0
- package/dist/types/utils.d.ts +5 -0
- package/dist/utils/index.d.ts +28 -0
- package/dist/utils/index.mjs +123 -0
- package/dist/utils/provider.d.ts +13 -0
- package/dist/utils/provider.mjs +360 -0
- package/package.json +5 -3
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { getProp, normalizePath, randomString, removeTokenPrefix, parseQuery } from "../../utils";
|
|
2
|
+
import { RefreshController, RequestHandler, ExpiredAuthSessionError, Token, RefreshToken } from "../inc/index.mjs";
|
|
3
|
+
import { joinURL, withQuery } from "ufo";
|
|
4
|
+
import { BaseScheme } from "./base.mjs";
|
|
5
|
+
import requrl from "requrl";
|
|
6
|
+
const DEFAULTS = {
|
|
7
|
+
name: "oauth2",
|
|
8
|
+
accessType: void 0,
|
|
9
|
+
redirectUri: void 0,
|
|
10
|
+
logoutRedirectUri: void 0,
|
|
11
|
+
clientId: void 0,
|
|
12
|
+
clientSecretTransport: "body",
|
|
13
|
+
audience: void 0,
|
|
14
|
+
grantType: void 0,
|
|
15
|
+
responseMode: void 0,
|
|
16
|
+
acrValues: void 0,
|
|
17
|
+
autoLogout: false,
|
|
18
|
+
endpoints: {
|
|
19
|
+
logout: void 0,
|
|
20
|
+
authorization: void 0,
|
|
21
|
+
token: void 0,
|
|
22
|
+
userInfo: void 0
|
|
23
|
+
},
|
|
24
|
+
scope: [],
|
|
25
|
+
token: {
|
|
26
|
+
property: "access_token",
|
|
27
|
+
expiresProperty: "expires_in",
|
|
28
|
+
type: "Bearer",
|
|
29
|
+
name: "Authorization",
|
|
30
|
+
maxAge: false,
|
|
31
|
+
global: true,
|
|
32
|
+
prefix: "_token.",
|
|
33
|
+
expirationPrefix: "_token_expiration.",
|
|
34
|
+
httpOnly: false
|
|
35
|
+
},
|
|
36
|
+
refreshToken: {
|
|
37
|
+
property: "refresh_token",
|
|
38
|
+
maxAge: 60 * 60 * 24 * 30,
|
|
39
|
+
prefix: "_refresh_token.",
|
|
40
|
+
expirationPrefix: "_refresh_token_expiration.",
|
|
41
|
+
httpOnly: false
|
|
42
|
+
},
|
|
43
|
+
user: {
|
|
44
|
+
property: false
|
|
45
|
+
},
|
|
46
|
+
responseType: "token",
|
|
47
|
+
codeChallengeMethod: false,
|
|
48
|
+
clientWindow: false,
|
|
49
|
+
clientWindowWidth: 400,
|
|
50
|
+
clientWindowHeight: 600
|
|
51
|
+
};
|
|
52
|
+
export class Oauth2Scheme extends BaseScheme {
|
|
53
|
+
req;
|
|
54
|
+
token;
|
|
55
|
+
refreshToken;
|
|
56
|
+
refreshController;
|
|
57
|
+
requestHandler;
|
|
58
|
+
#clientWindowReference;
|
|
59
|
+
constructor($auth, options, ...defaults) {
|
|
60
|
+
super($auth, options, ...defaults, DEFAULTS);
|
|
61
|
+
this.req = process.server ? $auth.ctx.ssrContext.event.node.req : void 0;
|
|
62
|
+
this.token = new Token(this, this.$auth.$storage);
|
|
63
|
+
this.refreshToken = new RefreshToken(this, this.$auth.$storage);
|
|
64
|
+
this.refreshController = new RefreshController(this);
|
|
65
|
+
this.requestHandler = new RequestHandler(this, process.server ? this.$auth.ctx.ssrContext.event.$http : this.$auth.ctx.$http, $auth);
|
|
66
|
+
this.#clientWindowReference = null;
|
|
67
|
+
}
|
|
68
|
+
get scope() {
|
|
69
|
+
return Array.isArray(this.options.scope) ? this.options.scope.join(" ") : this.options.scope;
|
|
70
|
+
}
|
|
71
|
+
get redirectURI() {
|
|
72
|
+
const basePath = this.$auth.ctx.$config.app.baseURL || "";
|
|
73
|
+
const path = normalizePath(basePath + "/" + this.$auth.options.redirect.callback, this.$auth.ctx);
|
|
74
|
+
return this.options.redirectUri || joinURL(process.server ? requrl(this.req) : globalThis.location.origin, path);
|
|
75
|
+
}
|
|
76
|
+
get logoutRedirectURI() {
|
|
77
|
+
return this.options.logoutRedirectUri || joinURL(process.server ? requrl(this.req) : globalThis.location.origin, this.$auth.options.redirect.logout);
|
|
78
|
+
}
|
|
79
|
+
check(checkStatus = false) {
|
|
80
|
+
const response = {
|
|
81
|
+
valid: false,
|
|
82
|
+
tokenExpired: false,
|
|
83
|
+
refreshTokenExpired: false,
|
|
84
|
+
isRefreshable: true
|
|
85
|
+
};
|
|
86
|
+
const token = this.token.sync();
|
|
87
|
+
this.refreshToken.sync();
|
|
88
|
+
if (!token) {
|
|
89
|
+
return response;
|
|
90
|
+
}
|
|
91
|
+
if (!checkStatus) {
|
|
92
|
+
response.valid = true;
|
|
93
|
+
return response;
|
|
94
|
+
}
|
|
95
|
+
const tokenStatus = this.token.status();
|
|
96
|
+
const refreshTokenStatus = this.refreshToken.status();
|
|
97
|
+
if (refreshTokenStatus.expired()) {
|
|
98
|
+
response.refreshTokenExpired = true;
|
|
99
|
+
return response;
|
|
100
|
+
}
|
|
101
|
+
if (tokenStatus.expired()) {
|
|
102
|
+
response.tokenExpired = true;
|
|
103
|
+
return response;
|
|
104
|
+
}
|
|
105
|
+
response.valid = true;
|
|
106
|
+
return response;
|
|
107
|
+
}
|
|
108
|
+
async mounted() {
|
|
109
|
+
const { tokenExpired, refreshTokenExpired } = this.check(true);
|
|
110
|
+
if (refreshTokenExpired || tokenExpired && this.options.autoLogout) {
|
|
111
|
+
this.$auth.reset();
|
|
112
|
+
}
|
|
113
|
+
this.requestHandler.initializeRequestInterceptor(
|
|
114
|
+
this.options.endpoints.token
|
|
115
|
+
);
|
|
116
|
+
const redirected = await this.#handleCallback();
|
|
117
|
+
if (!redirected) {
|
|
118
|
+
return this.$auth.fetchUserOnce();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
reset() {
|
|
122
|
+
this.$auth.setUser(false);
|
|
123
|
+
this.token.reset();
|
|
124
|
+
this.refreshToken.reset();
|
|
125
|
+
this.requestHandler.reset();
|
|
126
|
+
}
|
|
127
|
+
async login(options = {}) {
|
|
128
|
+
const opts = {
|
|
129
|
+
protocol: "oauth2",
|
|
130
|
+
response_type: this.options.responseType,
|
|
131
|
+
access_type: this.options.accessType,
|
|
132
|
+
client_id: this.options.clientId,
|
|
133
|
+
redirect_uri: this.redirectURI,
|
|
134
|
+
scope: this.scope,
|
|
135
|
+
// Note: The primary reason for using the state parameter is to mitigate CSRF attacks.
|
|
136
|
+
// https://auth0.com/docs/protocols/oauth2/oauth-state
|
|
137
|
+
state: options.state || randomString(10),
|
|
138
|
+
code_challenge_method: this.options.codeChallengeMethod,
|
|
139
|
+
clientWindow: this.options.clientWindow,
|
|
140
|
+
clientWindowWidth: this.options.clientWindowWidth,
|
|
141
|
+
clientWindowHeight: this.options.clientWindowHeight,
|
|
142
|
+
...options.params
|
|
143
|
+
};
|
|
144
|
+
if (!opts.code_challenge_method) {
|
|
145
|
+
delete opts.code_challenge_method;
|
|
146
|
+
}
|
|
147
|
+
if (this.options.organization) {
|
|
148
|
+
opts.organization = this.options.organization;
|
|
149
|
+
}
|
|
150
|
+
if (this.options.audience) {
|
|
151
|
+
opts.audience = this.options.audience;
|
|
152
|
+
}
|
|
153
|
+
if (opts.clientWindow) {
|
|
154
|
+
if (this.#clientWindowReference === null || this.#clientWindowReference?.closed) {
|
|
155
|
+
const windowFeatures = this.clientWindowFeatures(opts.clientWindowWidth, opts.clientWindowHeight);
|
|
156
|
+
this.#clientWindowReference = globalThis.open("about:blank", "oauth2-client-window", windowFeatures);
|
|
157
|
+
let strategy = this.$auth.$state.strategy;
|
|
158
|
+
let listener = this.clientWindowCallback.bind(this);
|
|
159
|
+
globalThis.addEventListener("message", listener);
|
|
160
|
+
let checkPopUpInterval = setInterval(() => {
|
|
161
|
+
if (this.#clientWindowReference?.closed || strategy !== this.$auth.$state.strategy) {
|
|
162
|
+
globalThis.removeEventListener("message", listener);
|
|
163
|
+
this.#clientWindowReference = null;
|
|
164
|
+
clearInterval(checkPopUpInterval);
|
|
165
|
+
}
|
|
166
|
+
}, 500);
|
|
167
|
+
} else {
|
|
168
|
+
this.#clientWindowReference.focus();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (opts.response_type.includes("token") || opts.response_type.includes("id_token")) {
|
|
172
|
+
opts.nonce = options.nonce || randomString(10);
|
|
173
|
+
}
|
|
174
|
+
if (opts.code_challenge_method) {
|
|
175
|
+
switch (opts.code_challenge_method) {
|
|
176
|
+
case "plain":
|
|
177
|
+
case "S256":
|
|
178
|
+
{
|
|
179
|
+
const state = this.generateRandomString();
|
|
180
|
+
this.$auth.$storage.setUniversal(this.name + ".pkce_state", state);
|
|
181
|
+
const codeVerifier = this.generateRandomString();
|
|
182
|
+
this.$auth.$storage.setUniversal(this.name + ".pkce_code_verifier", codeVerifier);
|
|
183
|
+
const codeChallenge = await this.pkceChallengeFromVerifier(codeVerifier, opts.code_challenge_method === "S256");
|
|
184
|
+
opts.code_challenge = globalThis.encodeURIComponent(codeChallenge);
|
|
185
|
+
}
|
|
186
|
+
break;
|
|
187
|
+
case "implicit":
|
|
188
|
+
default:
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (this.options.responseMode) {
|
|
193
|
+
opts.response_mode = this.options.responseMode;
|
|
194
|
+
}
|
|
195
|
+
if (this.options.acrValues) {
|
|
196
|
+
opts.acr_values = this.options.acrValues;
|
|
197
|
+
}
|
|
198
|
+
this.$auth.$storage.setUniversal(this.name + ".state", opts.state);
|
|
199
|
+
const url = withQuery(this.options.endpoints.authorization, opts);
|
|
200
|
+
if (opts.clientWindow) {
|
|
201
|
+
if (this.#clientWindowReference) {
|
|
202
|
+
this.#clientWindowReference.location = url;
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
globalThis.location.replace(url);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
clientWindowCallback(event) {
|
|
209
|
+
const isLogInSuccessful = !!event.data.isLoggedIn;
|
|
210
|
+
if (isLogInSuccessful) {
|
|
211
|
+
this.$auth.fetchUserOnce();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
clientWindowFeatures(clientWindowWidth, clientWindowHeight) {
|
|
215
|
+
const top = globalThis.top.outerHeight / 2 + globalThis.top.screenY - clientWindowHeight / 2;
|
|
216
|
+
const left = globalThis.top.outerWidth / 2 + globalThis.top.screenX - clientWindowWidth / 2;
|
|
217
|
+
return `toolbar=no, menubar=no, width=${clientWindowWidth}, height=${clientWindowHeight}, top=${top}, left=${left}`;
|
|
218
|
+
}
|
|
219
|
+
logout() {
|
|
220
|
+
if (this.options.endpoints.logout) {
|
|
221
|
+
const opts = {
|
|
222
|
+
client_id: this.options.clientId,
|
|
223
|
+
redirect_uri: this.logoutRedirectURI
|
|
224
|
+
};
|
|
225
|
+
const url = withQuery(this.options.endpoints.logout, opts);
|
|
226
|
+
globalThis.location.replace(url);
|
|
227
|
+
}
|
|
228
|
+
return this.$auth.reset();
|
|
229
|
+
}
|
|
230
|
+
async fetchUser() {
|
|
231
|
+
if (!this.check().valid) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (!this.options.endpoints.userInfo) {
|
|
235
|
+
this.$auth.setUser({});
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const response = await this.$auth.requestWith({
|
|
239
|
+
url: this.options.endpoints.userInfo
|
|
240
|
+
});
|
|
241
|
+
this.$auth.setUser(getProp(response._data, this.options.user.property));
|
|
242
|
+
}
|
|
243
|
+
async #handleCallback() {
|
|
244
|
+
const route = this.$auth.ctx.$router.currentRoute.value;
|
|
245
|
+
if (this.$auth.options.redirect && normalizePath(route.path, this.$auth.ctx) !== normalizePath(this.$auth.options.redirect.callback, this.$auth.ctx)) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (process.server) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const hash = parseQuery(route.hash.slice(1));
|
|
252
|
+
const parsedQuery = Object.assign({}, route.query, hash);
|
|
253
|
+
let token = parsedQuery[this.options.token.property];
|
|
254
|
+
let refreshToken;
|
|
255
|
+
let tokenExpiresIn = false;
|
|
256
|
+
if (this.options.refreshToken.property) {
|
|
257
|
+
refreshToken = parsedQuery[this.options.refreshToken.property];
|
|
258
|
+
}
|
|
259
|
+
const state = this.$auth.$storage.getUniversal(this.name + ".state");
|
|
260
|
+
this.$auth.$storage.setUniversal(this.name + ".state", null);
|
|
261
|
+
if (state && parsedQuery.state !== state) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (this.options.responseType.includes("code") && parsedQuery.code) {
|
|
265
|
+
let codeVerifier;
|
|
266
|
+
if (this.options.codeChallengeMethod && this.options.codeChallengeMethod !== "implicit") {
|
|
267
|
+
codeVerifier = this.$auth.$storage.getUniversal(this.name + ".pkce_code_verifier");
|
|
268
|
+
this.$auth.$storage.setUniversal(this.name + ".pkce_code_verifier", null);
|
|
269
|
+
}
|
|
270
|
+
const response = await this.$auth.request({
|
|
271
|
+
method: "POST",
|
|
272
|
+
url: this.options.endpoints.token,
|
|
273
|
+
baseURL: "",
|
|
274
|
+
headers: {
|
|
275
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
276
|
+
},
|
|
277
|
+
body: new URLSearchParams({
|
|
278
|
+
code: parsedQuery.code,
|
|
279
|
+
client_id: this.options.clientId,
|
|
280
|
+
redirect_uri: this.redirectURI,
|
|
281
|
+
response_type: this.options.responseType,
|
|
282
|
+
audience: this.options.audience,
|
|
283
|
+
grant_type: this.options.grantType,
|
|
284
|
+
code_verifier: codeVerifier
|
|
285
|
+
})
|
|
286
|
+
});
|
|
287
|
+
token = getProp(response._data, this.options.token.property) || token;
|
|
288
|
+
refreshToken = getProp(response._data, this.options.refreshToken.property) || refreshToken;
|
|
289
|
+
tokenExpiresIn = this.options.token?.maxAge || getProp(response._data, this.options.token.expiresProperty) || 1800;
|
|
290
|
+
}
|
|
291
|
+
if (!token || !token.length) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
this.token.set(token, tokenExpiresIn);
|
|
295
|
+
if (refreshToken && refreshToken.length) {
|
|
296
|
+
this.refreshToken.set(refreshToken);
|
|
297
|
+
}
|
|
298
|
+
if (this.options.clientWindow) {
|
|
299
|
+
if (globalThis.opener) {
|
|
300
|
+
globalThis.opener.postMessage({ isLoggedIn: true });
|
|
301
|
+
globalThis.close();
|
|
302
|
+
}
|
|
303
|
+
} else if (this.$auth.options.watchLoggedIn) {
|
|
304
|
+
this.$auth.redirect("home", false, false);
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async refreshTokens() {
|
|
309
|
+
const refreshToken = this.refreshToken.get();
|
|
310
|
+
if (!refreshToken && !this.options.refreshToken.httpOnly) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const refreshTokenStatus = this.refreshToken.status();
|
|
314
|
+
if (refreshTokenStatus.expired()) {
|
|
315
|
+
this.$auth.reset();
|
|
316
|
+
throw new ExpiredAuthSessionError();
|
|
317
|
+
}
|
|
318
|
+
this.requestHandler.clearHeader();
|
|
319
|
+
let body = new URLSearchParams({
|
|
320
|
+
refresh_token: removeTokenPrefix(refreshToken, this.options.token.type),
|
|
321
|
+
scope: this.scope,
|
|
322
|
+
client_id: this.options.clientId,
|
|
323
|
+
grant_type: "refresh_token",
|
|
324
|
+
redirect_uri: this.redirectURI
|
|
325
|
+
});
|
|
326
|
+
if (this.options.refreshToken.httpOnly) {
|
|
327
|
+
body.delete("refresh_token");
|
|
328
|
+
}
|
|
329
|
+
const response = await this.$auth.request({
|
|
330
|
+
method: "post",
|
|
331
|
+
url: this.options.endpoints.token,
|
|
332
|
+
baseURL: "",
|
|
333
|
+
headers: {
|
|
334
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
335
|
+
},
|
|
336
|
+
body
|
|
337
|
+
}).catch((error) => {
|
|
338
|
+
this.$auth.callOnError(error, { method: "refreshToken" });
|
|
339
|
+
return Promise.reject(error);
|
|
340
|
+
});
|
|
341
|
+
this.updateTokens(response);
|
|
342
|
+
return response;
|
|
343
|
+
}
|
|
344
|
+
updateTokens(response) {
|
|
345
|
+
let tokenExpiresIn = false;
|
|
346
|
+
const token = getProp(response._data, this.options.token.property);
|
|
347
|
+
const refreshToken = getProp(response._data, this.options.refreshToken.property);
|
|
348
|
+
tokenExpiresIn = this.options.token?.maxAge || getProp(response._data, this.options.token.expiresProperty) || 1800;
|
|
349
|
+
this.token.set(token, tokenExpiresIn);
|
|
350
|
+
if (refreshToken) {
|
|
351
|
+
this.refreshToken.set(refreshToken);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
async pkceChallengeFromVerifier(v, hashValue) {
|
|
355
|
+
if (hashValue) {
|
|
356
|
+
const hashed = await this.#sha256(v);
|
|
357
|
+
return this.#base64UrlEncode(hashed);
|
|
358
|
+
}
|
|
359
|
+
return v;
|
|
360
|
+
}
|
|
361
|
+
generateRandomString() {
|
|
362
|
+
const array = new Uint32Array(28);
|
|
363
|
+
globalThis.crypto.getRandomValues(array);
|
|
364
|
+
return Array.from(array, (dec) => ("0" + dec.toString(16)).slice(-2)).join("");
|
|
365
|
+
}
|
|
366
|
+
#sha256(plain) {
|
|
367
|
+
const encoder = new TextEncoder();
|
|
368
|
+
const data = encoder.encode(plain);
|
|
369
|
+
return globalThis.crypto.subtle.digest("SHA-256", data);
|
|
370
|
+
}
|
|
371
|
+
#base64UrlEncode(str) {
|
|
372
|
+
return btoa(String.fromCharCode.apply(null, new Uint8Array(str))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
373
|
+
}
|
|
374
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { HTTPResponse, SchemeCheck, SchemePartialOptions } from '../../types';
|
|
2
|
+
import type { Auth } from '..';
|
|
3
|
+
import { Oauth2Scheme, type Oauth2SchemeEndpoints, type Oauth2SchemeOptions } from './oauth2';
|
|
4
|
+
import { IdToken, ConfigurationDocument } from '../inc';
|
|
5
|
+
import { type IdTokenableSchemeOptions } from '../../types';
|
|
6
|
+
export interface OpenIDConnectSchemeEndpoints extends Oauth2SchemeEndpoints {
|
|
7
|
+
configuration: string;
|
|
8
|
+
}
|
|
9
|
+
export interface OpenIDConnectSchemeOptions extends Oauth2SchemeOptions, IdTokenableSchemeOptions {
|
|
10
|
+
fetchRemote: boolean;
|
|
11
|
+
endpoints: OpenIDConnectSchemeEndpoints;
|
|
12
|
+
}
|
|
13
|
+
export declare class OpenIDConnectScheme<OptionsT extends OpenIDConnectSchemeOptions = OpenIDConnectSchemeOptions> extends Oauth2Scheme<OptionsT> {
|
|
14
|
+
#private;
|
|
15
|
+
idToken: IdToken;
|
|
16
|
+
configurationDocument: ConfigurationDocument;
|
|
17
|
+
constructor($auth: Auth, options: SchemePartialOptions<OpenIDConnectSchemeOptions>, ...defaults: SchemePartialOptions<OpenIDConnectSchemeOptions>[]);
|
|
18
|
+
protected updateTokens(response: HTTPResponse<any>): void;
|
|
19
|
+
check(checkStatus?: boolean): SchemeCheck;
|
|
20
|
+
mounted(): Promise<void | HTTPResponse<any>>;
|
|
21
|
+
reset(): void;
|
|
22
|
+
logout(): void;
|
|
23
|
+
fetchUser(): Promise<void>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { Oauth2Scheme } from "./oauth2.mjs";
|
|
2
|
+
import { normalizePath, getProp, parseQuery } from "../../utils";
|
|
3
|
+
import { IdToken, ConfigurationDocument } from "../inc/index.mjs";
|
|
4
|
+
import { withQuery } from "ufo";
|
|
5
|
+
const DEFAULTS = {
|
|
6
|
+
name: "openIDConnect",
|
|
7
|
+
responseType: "code",
|
|
8
|
+
grantType: "authorization_code",
|
|
9
|
+
scope: ["openid", "profile", "offline_access"],
|
|
10
|
+
idToken: {
|
|
11
|
+
property: "id_token",
|
|
12
|
+
maxAge: 1800,
|
|
13
|
+
prefix: "_id_token.",
|
|
14
|
+
expirationPrefix: "_id_token_expiration.",
|
|
15
|
+
httpOnly: false
|
|
16
|
+
},
|
|
17
|
+
fetchRemote: false,
|
|
18
|
+
codeChallengeMethod: "S256"
|
|
19
|
+
};
|
|
20
|
+
export class OpenIDConnectScheme extends Oauth2Scheme {
|
|
21
|
+
idToken;
|
|
22
|
+
configurationDocument;
|
|
23
|
+
constructor($auth, options, ...defaults) {
|
|
24
|
+
super($auth, options, ...defaults, DEFAULTS);
|
|
25
|
+
this.idToken = new IdToken(this, this.$auth.$storage);
|
|
26
|
+
this.configurationDocument = new ConfigurationDocument(this, this.$auth.$storage);
|
|
27
|
+
}
|
|
28
|
+
updateTokens(response) {
|
|
29
|
+
super.updateTokens(response);
|
|
30
|
+
const idToken = getProp(response._data, this.options.idToken.property);
|
|
31
|
+
if (idToken) {
|
|
32
|
+
this.idToken.set(idToken);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
check(checkStatus = false) {
|
|
36
|
+
const response = {
|
|
37
|
+
valid: false,
|
|
38
|
+
tokenExpired: false,
|
|
39
|
+
refreshTokenExpired: false,
|
|
40
|
+
idTokenExpired: false,
|
|
41
|
+
isRefreshable: true
|
|
42
|
+
};
|
|
43
|
+
const token = this.token.sync();
|
|
44
|
+
this.refreshToken.sync();
|
|
45
|
+
this.idToken.sync();
|
|
46
|
+
if (!token) {
|
|
47
|
+
return response;
|
|
48
|
+
}
|
|
49
|
+
if (!checkStatus) {
|
|
50
|
+
response.valid = true;
|
|
51
|
+
return response;
|
|
52
|
+
}
|
|
53
|
+
const tokenStatus = this.token.status();
|
|
54
|
+
const refreshTokenStatus = this.refreshToken.status();
|
|
55
|
+
const idTokenStatus = this.idToken.status();
|
|
56
|
+
if (refreshTokenStatus.expired()) {
|
|
57
|
+
response.refreshTokenExpired = true;
|
|
58
|
+
return response;
|
|
59
|
+
}
|
|
60
|
+
if (tokenStatus.expired()) {
|
|
61
|
+
response.tokenExpired = true;
|
|
62
|
+
return response;
|
|
63
|
+
}
|
|
64
|
+
if (idTokenStatus.expired()) {
|
|
65
|
+
response.idTokenExpired = true;
|
|
66
|
+
return response;
|
|
67
|
+
}
|
|
68
|
+
response.valid = true;
|
|
69
|
+
return response;
|
|
70
|
+
}
|
|
71
|
+
async mounted() {
|
|
72
|
+
await this.configurationDocument.init();
|
|
73
|
+
const { tokenExpired, refreshTokenExpired } = this.check(true);
|
|
74
|
+
if (refreshTokenExpired || tokenExpired && this.options.autoLogout) {
|
|
75
|
+
this.$auth.reset();
|
|
76
|
+
}
|
|
77
|
+
this.requestHandler.initializeRequestInterceptor(this.options.endpoints.token);
|
|
78
|
+
const redirected = await this.#handleCallback();
|
|
79
|
+
if (!redirected) {
|
|
80
|
+
return this.$auth.fetchUserOnce();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
reset() {
|
|
84
|
+
this.$auth.setUser(false);
|
|
85
|
+
this.token.reset();
|
|
86
|
+
this.idToken.reset();
|
|
87
|
+
this.refreshToken.reset();
|
|
88
|
+
this.requestHandler.reset();
|
|
89
|
+
this.configurationDocument.reset();
|
|
90
|
+
}
|
|
91
|
+
logout() {
|
|
92
|
+
if (this.options.endpoints.logout) {
|
|
93
|
+
const opts = {
|
|
94
|
+
id_token_hint: this.idToken.get(),
|
|
95
|
+
post_logout_redirect_uri: this.logoutRedirectURI
|
|
96
|
+
};
|
|
97
|
+
const url = withQuery(this.options.endpoints.logout, opts);
|
|
98
|
+
globalThis.location.replace(url);
|
|
99
|
+
}
|
|
100
|
+
return this.$auth.reset();
|
|
101
|
+
}
|
|
102
|
+
async fetchUser() {
|
|
103
|
+
if (!this.check().valid) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (!this.options.fetchRemote && this.idToken.get()) {
|
|
107
|
+
const data2 = this.idToken.userInfo();
|
|
108
|
+
this.$auth.setUser(data2);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (!this.options.endpoints.userInfo) {
|
|
112
|
+
this.$auth.setUser({});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const data = await this.$auth.requestWith({
|
|
116
|
+
url: this.options.endpoints.userInfo
|
|
117
|
+
});
|
|
118
|
+
this.$auth.setUser(data._data);
|
|
119
|
+
}
|
|
120
|
+
async #handleCallback() {
|
|
121
|
+
const route = this.$auth.ctx.$router.currentRoute.value;
|
|
122
|
+
if (this.$auth.options.redirect && normalizePath(route.path, this.$auth.ctx) !== normalizePath(this.$auth.options.redirect.callback, this.$auth.ctx)) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (process.server) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const hash = parseQuery(route.hash.slice(1));
|
|
129
|
+
const parsedQuery = Object.assign({}, route.query, hash);
|
|
130
|
+
let token = parsedQuery[this.options.token.property];
|
|
131
|
+
let tokenExpiresIn = false;
|
|
132
|
+
let refreshToken;
|
|
133
|
+
if (this.options.refreshToken.property) {
|
|
134
|
+
refreshToken = parsedQuery[this.options.refreshToken.property];
|
|
135
|
+
}
|
|
136
|
+
let idToken = parsedQuery[this.options.idToken.property];
|
|
137
|
+
const state = this.$auth.$storage.getUniversal(this.name + ".state");
|
|
138
|
+
this.$auth.$storage.setUniversal(this.name + ".state", null);
|
|
139
|
+
if (state && parsedQuery.state !== state) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (this.options.responseType.includes("code") && parsedQuery.code) {
|
|
143
|
+
let codeVerifier;
|
|
144
|
+
if (this.options.codeChallengeMethod && this.options.codeChallengeMethod !== "implicit") {
|
|
145
|
+
codeVerifier = this.$auth.$storage.getUniversal(this.name + ".pkce_code_verifier");
|
|
146
|
+
this.$auth.$storage.setUniversal(this.name + ".pkce_code_verifier", null);
|
|
147
|
+
}
|
|
148
|
+
const response = await this.$auth.request({
|
|
149
|
+
method: "post",
|
|
150
|
+
url: this.options.endpoints.token,
|
|
151
|
+
baseURL: "",
|
|
152
|
+
headers: {
|
|
153
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
154
|
+
},
|
|
155
|
+
body: new URLSearchParams({
|
|
156
|
+
code: parsedQuery.code,
|
|
157
|
+
client_id: this.options.clientId,
|
|
158
|
+
redirect_uri: this.redirectURI,
|
|
159
|
+
response_type: this.options.responseType,
|
|
160
|
+
audience: this.options.audience,
|
|
161
|
+
grant_type: this.options.grantType,
|
|
162
|
+
code_verifier: codeVerifier
|
|
163
|
+
})
|
|
164
|
+
});
|
|
165
|
+
token = getProp(response._data, this.options.token.property) || token;
|
|
166
|
+
tokenExpiresIn = this.options.token?.maxAge || getProp(response._data, this.options.token.expiresProperty) || 1800;
|
|
167
|
+
refreshToken = getProp(response._data, this.options.refreshToken.property) || refreshToken;
|
|
168
|
+
idToken = getProp(response._data, this.options.idToken.property) || idToken;
|
|
169
|
+
}
|
|
170
|
+
if (!token || !token.length) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
this.token.set(token, tokenExpiresIn);
|
|
174
|
+
if (refreshToken && refreshToken.length) {
|
|
175
|
+
this.refreshToken.set(refreshToken);
|
|
176
|
+
}
|
|
177
|
+
if (idToken && idToken.length) {
|
|
178
|
+
this.idToken.set(idToken);
|
|
179
|
+
}
|
|
180
|
+
if (this.options.clientWindow) {
|
|
181
|
+
if (globalThis.opener) {
|
|
182
|
+
globalThis.opener.postMessage({ isLoggedIn: true });
|
|
183
|
+
globalThis.close();
|
|
184
|
+
}
|
|
185
|
+
} else if (this.$auth.options.watchLoggedIn) {
|
|
186
|
+
this.$auth.redirect("home", false, false);
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { HTTPRequest, HTTPResponse, RefreshableScheme, RefreshableSchemeOptions, SchemeCheck, SchemePartialOptions } from '../../types';
|
|
2
|
+
import type { Auth } from '..';
|
|
3
|
+
import { RefreshController, RefreshToken } from '../inc';
|
|
4
|
+
import { LocalScheme, type LocalSchemeEndpoints, type LocalSchemeOptions } from './local';
|
|
5
|
+
export interface RefreshSchemeEndpoints extends LocalSchemeEndpoints {
|
|
6
|
+
refresh: HTTPRequest;
|
|
7
|
+
}
|
|
8
|
+
export interface RefreshSchemeOptions extends LocalSchemeOptions, RefreshableSchemeOptions {
|
|
9
|
+
endpoints: RefreshSchemeEndpoints;
|
|
10
|
+
autoLogout: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare class RefreshScheme<OptionsT extends RefreshSchemeOptions = RefreshSchemeOptions> extends LocalScheme<OptionsT> implements RefreshableScheme<OptionsT> {
|
|
13
|
+
refreshToken: RefreshToken;
|
|
14
|
+
refreshController: RefreshController;
|
|
15
|
+
constructor($auth: Auth, options: SchemePartialOptions<RefreshSchemeOptions>);
|
|
16
|
+
check(checkStatus?: boolean): SchemeCheck;
|
|
17
|
+
mounted(): Promise<HTTPResponse<any> | void>;
|
|
18
|
+
refreshTokens(): Promise<HTTPResponse<any> | void>;
|
|
19
|
+
setUserToken(token: string | boolean, refreshToken?: string | boolean): Promise<HTTPResponse<any> | void>;
|
|
20
|
+
reset({ resetInterceptor }?: {
|
|
21
|
+
resetInterceptor?: boolean | undefined;
|
|
22
|
+
}): void;
|
|
23
|
+
protected extractRefreshToken(response: HTTPResponse<any>): string;
|
|
24
|
+
protected updateTokens(response: HTTPResponse<any>): void;
|
|
25
|
+
protected initializeRequestInterceptor(): void;
|
|
26
|
+
}
|