chamesu
Version:
This package contains all packages of the chat application.
249 lines (190 loc) • 7.88 kB
text/typescript
/*
* 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<typeof setTimeout>;
protected storeKeys : string[] = [
'token',
'provider',
'user',
'permissions'
];
protected abilityManager!: AbilityManager;
protected identifyPromise : Promise<Record<string, any>> | 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<this.storeKeys.length; i++) {
const key = this.storeKeys[i];
const keyWellFormed = key.charAt(0).toLocaleUpperCase() + key.slice(1);
const commitName = 'auth/set' + keyWellFormed;
const value = this.ctx.$authWarehouse.get(key);
if(typeof value !== 'undefined') {
(this.ctx.store as Store<any>).commit(commitName, value);
}
}
}
private subscribeStore() {
if(typeof this.ctx === 'undefined') return;
(this.ctx.store as Store<any>).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 = <AuthStoreToken> 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<Record<string, any>|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<string, any> = {
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<string, any>) : Promise<Record<string, any>> {
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<any>[]) {
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<Oauth2TokenResponse> {
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<Oauth2TokenResponse> {
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<Record<string, any>> {
const data = await this.client.getUserInfo(token);
// change key case, because all user objects are in that format.
return changeResponseKeyCase(data);
}
}
export default AuthModule;