138 lines
4.7 KiB
TypeScript
138 lines
4.7 KiB
TypeScript
import * as plugins from '../plugins.js';
|
|
|
|
import type { Cloudly } from '../classes.cloudly.js';
|
|
import { logger } from '../logger.js';
|
|
import { Authorization } from './classes.authorization.js';
|
|
import { User } from './classes.user.js';
|
|
|
|
export interface IJwtData {
|
|
userId: string;
|
|
status: 'loggedIn' | 'loggedOut';
|
|
expiresAt: number;
|
|
}
|
|
|
|
export class CloudlyAuthManager {
|
|
cloudlyRef: Cloudly;
|
|
public get db() {
|
|
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
|
}
|
|
public CUser = plugins.smartdata.setDefaultManagerForDoc(this, User);
|
|
public CAuthorization = plugins.smartdata.setDefaultManagerForDoc(this, Authorization);
|
|
|
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
public smartjwtInstance: plugins.smartjwt.SmartJwt<IJwtData>;
|
|
|
|
constructor(cloudlyRef: Cloudly) {
|
|
this.cloudlyRef = cloudlyRef;
|
|
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
|
|
}
|
|
|
|
public async createNewSecureToken() {
|
|
return plugins.smartunique.uniSimple('secureToken', 64);
|
|
}
|
|
|
|
public async start() {
|
|
// lets setup the smartjwtInstance
|
|
this.smartjwtInstance = new plugins.smartjwt.SmartJwt();
|
|
await this.smartjwtInstance.init();
|
|
const kvStore = await this.cloudlyRef.config.appData.getKvStore();
|
|
|
|
const existingJwtKeys: plugins.tsclass.network.IJwtKeypair = (await kvStore.readKey(
|
|
'jwtKeypair',
|
|
)) as plugins.tsclass.network.IJwtKeypair;
|
|
|
|
if (!existingJwtKeys) {
|
|
await this.smartjwtInstance.createNewKeyPair();
|
|
const newJwtKeys = this.smartjwtInstance.getKeyPairAsJson();
|
|
await kvStore.writeKey('jwtKeypair', newJwtKeys);
|
|
} else {
|
|
this.smartjwtInstance.setKeyPairAsJson(existingJwtKeys);
|
|
}
|
|
|
|
this.typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_LoginWithUsernameAndPassword>(
|
|
'adminLoginWithUsernameAndPassword',
|
|
async (dataArg) => {
|
|
let jwt: string;
|
|
let expiresAtTimestamp: number = Date.now() + 3600 * 1000 * 24 * 7;
|
|
const user = await User.findUserByUsernameAndPassword(dataArg.username, dataArg.password);
|
|
if (!user) {
|
|
logger.log('warn', 'login failed');
|
|
throw new plugins.typedrequest.TypedResponseError('login failed');
|
|
} else {
|
|
jwt = await this.smartjwtInstance.createJWT({
|
|
userId: user.id,
|
|
status: 'loggedIn',
|
|
expiresAt: expiresAtTimestamp,
|
|
});
|
|
logger.log('success', 'login successful');
|
|
}
|
|
return {
|
|
identity: {
|
|
jwt,
|
|
userId: user.id,
|
|
name: user.data.username,
|
|
expiresAt: expiresAtTimestamp,
|
|
role: user.data.role,
|
|
type: user.data.type,
|
|
},
|
|
};
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
public async stop() {}
|
|
|
|
public validIdentityGuard = new plugins.smartguard.Guard<{
|
|
identity: plugins.servezoneInterfaces.data.IIdentity;
|
|
}>(
|
|
async (dataArg) => {
|
|
const jwt = dataArg.identity.jwt;
|
|
const jwtData: IJwtData = await this.smartjwtInstance.verifyJWTAndGetData(jwt);
|
|
const expired = jwtData.expiresAt < Date.now();
|
|
plugins.smartexpect
|
|
.expect(jwtData.status)
|
|
.setFailMessage('user not logged in')
|
|
.toEqual('loggedIn');
|
|
plugins.smartexpect.expect(expired).setFailMessage(`jwt expired`).toBeFalse();
|
|
plugins.smartexpect
|
|
.expect(dataArg.identity.expiresAt)
|
|
.setFailMessage(
|
|
`expiresAt >>identity valid until:${dataArg.identity.expiresAt}, but jwt says: ${jwtData.expiresAt}<< has been tampered with`,
|
|
)
|
|
.toEqual(jwtData.expiresAt);
|
|
plugins.smartexpect
|
|
.expect(dataArg.identity.userId)
|
|
.setFailMessage('userId has been tampered with')
|
|
.toEqual(jwtData.userId);
|
|
if (expired) {
|
|
throw new Error('identity is expired');
|
|
}
|
|
return true;
|
|
},
|
|
{
|
|
failedHint: 'identity is not valid.',
|
|
name: 'validIdentityGuard',
|
|
},
|
|
);
|
|
|
|
public adminIdentityGuard = new plugins.smartguard.Guard<{
|
|
identity: plugins.servezoneInterfaces.data.IIdentity;
|
|
}>(
|
|
async (dataArg) => {
|
|
await plugins.smartguard.passGuardsOrReject(dataArg, [this.validIdentityGuard]);
|
|
const jwt = dataArg.identity.jwt;
|
|
const jwtData: IJwtData = await this.smartjwtInstance.verifyJWTAndGetData(jwt);
|
|
const user = await this.CUser.getInstance({ id: jwtData.userId });
|
|
const isAdminBool = user.data.role === 'admin';
|
|
console.log(`user is admin: ${isAdminBool}`);
|
|
return isAdminBool;
|
|
},
|
|
{
|
|
failedHint: 'user is not admin.',
|
|
name: 'adminIdentityGuard',
|
|
},
|
|
);
|
|
}
|