/* * Copyright (c) 2022. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ import {Context} from "@nuxt/types"; import {Store} from 'vuex'; import {AbilityManager, OwnedPermission, Oauth2TokenResponse, Oauth2Client} from "@typescript-auth/core"; import {mapOnAllApis} from "~/modules/api"; import BaseApi from "~/modules/api/base"; import {AuthStoreToken} from "~/store/auth"; import {changeResponseKeyCase} from "~/modules/api/utils"; export type AuthModuleOptions = { tokenHost: string, tokenPath: string, userInfoPath: string } class AuthModule { protected ctx: Context; protected client: Oauth2Client; protected refreshTokenJob: undefined | ReturnType; protected storeKeys : string[] = [ 'token', 'provider', 'user', 'permissions' ]; protected abilityManager!: AbilityManager; protected identifyPromise : Promise> | undefined; // -------------------------------------------------------------------- constructor(ctx: Context, options: AuthModuleOptions) { this.ctx = ctx; this.client = new Oauth2Client({ token_host: options.tokenHost, token_path: options.tokenPath, user_info_path: options.userInfoPath, client_id: 'user-interface' }) this.abilityManager = new AbilityManager([]); this.subscribeStore(); this.initStore(); this.initFromStore(); } // -------------------------------------------------------------------- private initFromStore() { const permissions = this.ctx.store.getters['auth/permissions']; this.setPermissions(permissions); } private initStore() { if(typeof this.ctx === 'undefined' || typeof this.ctx.store === 'undefined') return; for(let i=0; i).commit(commitName, value); } } } private subscribeStore() { if(typeof this.ctx === 'undefined') return; (this.ctx.store as Store).subscribe((mutation: any) => { switch(mutation.type) { case 'auth/setPermissions': this.setPermissions(mutation.payload); break; case 'auth/unsetPermissions': this.setPermissions([]); break; case 'auth/setToken': let token = mutation.payload; if(this.refreshTokenJob) { clearTimeout(this.refreshTokenJob); } let callback = () => { if(typeof this.ctx !== 'undefined') { this.ctx.store.dispatch('auth/triggerTokenExpired') .then((r: any) => r) .catch(() => this.ctx.redirect('/logout')); } }; callback.bind(this); this.setRequestToken(token.access_token); if(typeof token.expire_date !== 'undefined') { let expireDateInTime = (new Date(token.expire_date)).getTime(); let currentTime = (new Date()).getTime(); let timeoutMilliSeconds = expireDateInTime - currentTime; if (timeoutMilliSeconds < 0) { callback(); } this.refreshTokenJob = setTimeout(callback, timeoutMilliSeconds); } break; case 'auth/unsetToken': if(this.refreshTokenJob) { clearTimeout(this.refreshTokenJob); } break; } }) } // -------------------------------------------------------------------- public resolveMe() : Promise|undefined> { if(typeof this.identifyPromise !== 'undefined') { return this.identifyPromise; } const token : AuthStoreToken | undefined = this.ctx.store.getters['auth/token']; if(!token) return new Promise(((resolve) => resolve(undefined))); const resolved = this.ctx.store.getters['auth/permissionsResolved']; if(typeof resolved !== 'undefined' && resolved) { const userInfo : Record = { permissions: this.ctx.store.getters['auth/permissions'], ...this.ctx.store.getters['auth/user'] }; return new Promise(((resolve) => resolve(userInfo))); } this.identifyPromise = this.getUserInfo(token.access_token) .then(this.handleUserInfoResponse.bind(this)); return this.identifyPromise; }; private async handleUserInfoResponse(userInfo: Record) : Promise> { const {permissions, ...user} = userInfo; await this.ctx.store.commit('auth/setPermissionsResolved', true); await this.ctx.store.dispatch('auth/triggerSetUser', user); await this.ctx.store.dispatch('auth/triggerSetPermissions', permissions); return user; } // -------------------------------------------------------------------- public can(action: string, subject: any, field?: string) { return this.abilityManager.can(action, subject, field); } public setPermissions(permissions: OwnedPermission[]) { this.abilityManager.setPermissions(permissions); } // -------------------------------------------------------------------- public setRequestToken = (token: string) => { mapOnAllApis((api: BaseApi) => { api.setAuthorizationBearerHeader(token); api.mountAuthResponseInterceptor(); return api; }); }; public unsetRequestToken = () => { mapOnAllApis((api: BaseApi) => { api.unsetAuthorizationBearerHeader(); api.unMountAuthResponseInterceptor(); return api; }); } // -------------------------------------------------------------------- /** * Get access token with given credentials. * * @param username * @param password */ public async getTokenWithPassword(username: string, password: string) : Promise { const data = await this.client.getTokenWithPasswordGrant({ username, password }) this.setRequestToken(data.access_token); return data; } /** * Refresh access token with a given refresh token. * * @param token */ public async getTokenWithRefreshToken(token: string) : Promise { const data = await this.client.getTokenWithRefreshToken({ refresh_token: token }); this.setRequestToken(data.access_token); return data; } /** * Get user info for a given token. * * @param token */ public async getUserInfo(token: string) : Promise> { const data = await this.client.getUserInfo(token); // change key case, because all user objects are in that format. return changeResponseKeyCase(data); } } export default AuthModule;