@lucasroll62/nuxt3-auth
Version:
An alternative module to @nuxtjs/auth
187 lines (186 loc) • 6.05 kB
JavaScript
import { Oauth2Scheme } from "./oauth2.mjs";
import { normalizePath, getProp, parseQuery } from "../../utils";
import { IdToken, ConfigurationDocument } from "../inc/index.mjs";
import { useRoute } from "#imports";
import { withQuery } from "ufo";
const DEFAULTS = {
name: "openIDConnect",
responseType: "code",
grantType: "authorization_code",
scope: ["openid", "profile", "offline_access"],
idToken: {
property: "id_token",
maxAge: 1800,
prefix: "_id_token.",
expirationPrefix: "_id_token_expiration."
},
fetchRemote: false,
codeChallengeMethod: "S256"
};
export class OpenIDConnectScheme extends Oauth2Scheme {
constructor($auth, options, ...defaults) {
super($auth, options, ...defaults, DEFAULTS);
this.idToken = new IdToken(this, this.$auth.$storage);
this.configurationDocument = new ConfigurationDocument(this, this.$auth.$storage);
}
updateTokens(response) {
super.updateTokens(response);
const idToken = getProp(response, this.options.idToken.property);
if (idToken) {
this.idToken.set(idToken);
}
}
check(checkStatus = false) {
const response = {
valid: false,
tokenExpired: false,
refreshTokenExpired: false,
idTokenExpired: false,
isRefreshable: true
};
const token = this.token.sync();
this.refreshToken.sync();
this.idToken.sync();
if (!token) {
return response;
}
if (!checkStatus) {
response.valid = true;
return response;
}
const tokenStatus = this.token.status();
const refreshTokenStatus = this.refreshToken.status();
const idTokenStatus = this.idToken.status();
if (refreshTokenStatus.expired()) {
response.refreshTokenExpired = true;
return response;
}
if (tokenStatus.expired()) {
response.tokenExpired = true;
return response;
}
if (idTokenStatus.expired()) {
response.idTokenExpired = true;
return response;
}
response.valid = true;
return response;
}
async mounted() {
await this.configurationDocument.init();
const { tokenExpired, refreshTokenExpired } = this.check(true);
if (refreshTokenExpired || tokenExpired && this.options.autoLogout) {
this.$auth.reset();
}
this.requestHandler.initializeRequestInterceptor(this.options.endpoints.token);
const redirected = await this.#handleCallback();
if (!redirected) {
return this.$auth.fetchUserOnce();
}
}
reset() {
this.$auth.setUser(false);
this.token.reset();
this.idToken.reset();
this.refreshToken.reset();
this.requestHandler.reset();
this.configurationDocument.reset();
}
logout() {
if (this.options.endpoints.logout) {
const opts = {
id_token_hint: this.idToken.get(),
post_logout_redirect_uri: this.logoutRedirectURI
};
const url = withQuery(this.options.endpoints.logout, opts);
window.location.replace(url);
}
return this.$auth.reset();
}
async fetchUser() {
if (!this.check().valid) {
return;
}
if (!this.options.fetchRemote && this.idToken.get()) {
const data2 = this.idToken.userInfo();
this.$auth.setUser(data2);
return;
}
if (!this.options.endpoints.userInfo) {
this.$auth.setUser({});
return;
}
const data = await this.$auth.requestWith({
url: this.options.endpoints.userInfo
});
this.$auth.setUser(data);
}
async #handleCallback() {
const route = useRoute();
if (this.$auth.options.redirect && normalizePath(route.path) !== normalizePath(this.$auth.options.redirect.callback)) {
return;
}
if (process.server) {
return;
}
const hash = parseQuery(route.hash.slice(1));
const parsedQuery = Object.assign({}, route.query, hash);
let token = parsedQuery[this.options.token.property];
let refreshToken;
if (this.options.refreshToken.property) {
refreshToken = parsedQuery[this.options.refreshToken.property];
}
let idToken = parsedQuery[this.options.idToken.property];
const state = this.$auth.$storage.getUniversal(this.name + ".state");
this.$auth.$storage.setUniversal(this.name + ".state", null);
if (state && parsedQuery.state !== state) {
return;
}
if (this.options.responseType.includes("code") && parsedQuery.code) {
let codeVerifier;
if (this.options.codeChallengeMethod && this.options.codeChallengeMethod !== "implicit") {
codeVerifier = this.$auth.$storage.getUniversal(this.name + ".pkce_code_verifier");
this.$auth.$storage.setUniversal(this.name + ".pkce_code_verifier", null);
}
const response = await this.$auth.request({
method: "post",
url: this.options.endpoints.token,
baseURL: "",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: new URLSearchParams({
code: parsedQuery.code,
client_id: this.options.clientId,
redirect_uri: this.redirectURI,
response_type: this.options.responseType,
audience: this.options.audience,
grant_type: this.options.grantType,
code_verifier: codeVerifier
})
});
token = getProp(response, this.options.token.property) || token;
refreshToken = getProp(response, this.options.refreshToken.property) || refreshToken;
idToken = getProp(response, this.options.idToken.property) || idToken;
}
if (!token || !token.length) {
return;
}
this.token.set(token);
if (refreshToken && refreshToken.length) {
this.refreshToken.set(refreshToken);
}
if (idToken && idToken.length) {
this.idToken.set(idToken);
}
if (this.options.clientWindow) {
if (globalThis.opener) {
globalThis.opener.postMessage({ isLoggedIn: true });
globalThis.close();
}
} else if (this.$auth.options.watchLoggedIn) {
this.$auth.redirect("home", false, false);
return true;
}
}
}