240 lines
6.6 KiB
TypeScript
240 lines
6.6 KiB
TypeScript
import * as plugins from '../../plugins.js';
|
|
import type { OpsServer } from '../classes.opsserver.js';
|
|
import * as interfaces from '../../../ts_interfaces/index.js';
|
|
|
|
export interface IJwtData {
|
|
userId: string;
|
|
status: 'loggedIn' | 'loggedOut';
|
|
expiresAt: number;
|
|
}
|
|
|
|
export class AdminHandler {
|
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
|
|
// JWT instance
|
|
public smartjwtInstance: plugins.smartjwt.SmartJwt<IJwtData>;
|
|
|
|
// Simple in-memory user storage (in production, use proper database)
|
|
private users = new Map<string, {
|
|
id: string;
|
|
username: string;
|
|
password: string;
|
|
role: string;
|
|
}>();
|
|
|
|
constructor(private opsServerRef: OpsServer) {
|
|
// Add this handler's router to the parent
|
|
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
|
}
|
|
|
|
public async initialize(): Promise<void> {
|
|
await this.initializeJwt();
|
|
this.initializeDefaultUsers();
|
|
this.registerHandlers();
|
|
}
|
|
|
|
private async initializeJwt(): Promise<void> {
|
|
this.smartjwtInstance = new plugins.smartjwt.SmartJwt();
|
|
await this.smartjwtInstance.init();
|
|
|
|
// For development, create new keypair each time
|
|
// In production, load from storage like cloudly does
|
|
await this.smartjwtInstance.createNewKeyPair();
|
|
}
|
|
|
|
private initializeDefaultUsers(): void {
|
|
// Add default admin user
|
|
const adminId = plugins.uuid.v4();
|
|
this.users.set(adminId, {
|
|
id: adminId,
|
|
username: 'admin',
|
|
password: 'admin',
|
|
role: 'admin',
|
|
});
|
|
}
|
|
|
|
private registerHandlers(): void {
|
|
// Admin Login Handler
|
|
this.typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_AdminLoginWithUsernameAndPassword>(
|
|
'adminLoginWithUsernameAndPassword',
|
|
async (dataArg) => {
|
|
try {
|
|
// Find user by username and password
|
|
let user: { id: string; username: string; password: string; role: string } | null = null;
|
|
for (const [_, userData] of this.users) {
|
|
if (userData.username === dataArg.username && userData.password === dataArg.password) {
|
|
user = userData;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!user) {
|
|
throw new plugins.typedrequest.TypedResponseError('login failed');
|
|
}
|
|
|
|
const expiresAtTimestamp = Date.now() + 3600 * 1000 * 24 * 7; // 7 days
|
|
|
|
const jwt = await this.smartjwtInstance.createJWT({
|
|
userId: user.id,
|
|
status: 'loggedIn',
|
|
expiresAt: expiresAtTimestamp,
|
|
});
|
|
|
|
return {
|
|
identity: {
|
|
jwt,
|
|
userId: user.id,
|
|
name: user.username,
|
|
expiresAt: expiresAtTimestamp,
|
|
role: user.role,
|
|
type: 'user',
|
|
},
|
|
};
|
|
} catch (error) {
|
|
if (error instanceof plugins.typedrequest.TypedResponseError) {
|
|
throw error;
|
|
}
|
|
throw new plugins.typedrequest.TypedResponseError('login failed');
|
|
}
|
|
}
|
|
)
|
|
);
|
|
|
|
// Admin Logout Handler
|
|
this.typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_AdminLogout>(
|
|
'adminLogout',
|
|
async (dataArg) => {
|
|
// In a real implementation, you might want to blacklist the JWT
|
|
// For now, just return success
|
|
return {
|
|
success: true,
|
|
};
|
|
}
|
|
)
|
|
);
|
|
|
|
// Verify Identity Handler
|
|
this.typedrouter.addTypedHandler(
|
|
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_VerifyIdentity>(
|
|
'verifyIdentity',
|
|
async (dataArg) => {
|
|
if (!dataArg.identity?.jwt) {
|
|
return {
|
|
valid: false,
|
|
};
|
|
}
|
|
|
|
try {
|
|
const jwtData = await this.smartjwtInstance.verifyJWTAndGetData(dataArg.identity.jwt);
|
|
|
|
// Check if expired
|
|
if (jwtData.expiresAt < Date.now()) {
|
|
return {
|
|
valid: false,
|
|
};
|
|
}
|
|
|
|
// Check if logged in
|
|
if (jwtData.status !== 'loggedIn') {
|
|
return {
|
|
valid: false,
|
|
};
|
|
}
|
|
|
|
// Find user
|
|
const user = this.users.get(jwtData.userId);
|
|
if (!user) {
|
|
return {
|
|
valid: false,
|
|
};
|
|
}
|
|
|
|
return {
|
|
valid: true,
|
|
identity: {
|
|
jwt: dataArg.identity.jwt,
|
|
userId: user.id,
|
|
name: user.username,
|
|
expiresAt: jwtData.expiresAt,
|
|
role: user.role,
|
|
type: 'user',
|
|
},
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
valid: false,
|
|
};
|
|
}
|
|
}
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Create a guard for valid identity (matching cloudly pattern)
|
|
*/
|
|
public validIdentityGuard = new plugins.smartguard.Guard<{
|
|
identity: interfaces.data.IIdentity;
|
|
}>(
|
|
async (dataArg) => {
|
|
if (!dataArg.identity?.jwt) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
const jwtData = await this.smartjwtInstance.verifyJWTAndGetData(dataArg.identity.jwt);
|
|
|
|
// Check expiration
|
|
if (jwtData.expiresAt < Date.now()) {
|
|
return false;
|
|
}
|
|
|
|
// Check status
|
|
if (jwtData.status !== 'loggedIn') {
|
|
return false;
|
|
}
|
|
|
|
// Verify data hasn't been tampered with
|
|
if (dataArg.identity.expiresAt !== jwtData.expiresAt) {
|
|
return false;
|
|
}
|
|
|
|
if (dataArg.identity.userId !== jwtData.userId) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
},
|
|
{
|
|
failedHint: 'identity is not valid',
|
|
name: 'validIdentityGuard',
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Create a guard for admin identity (matching cloudly pattern)
|
|
*/
|
|
public adminIdentityGuard = new plugins.smartguard.Guard<{
|
|
identity: interfaces.data.IIdentity;
|
|
}>(
|
|
async (dataArg) => {
|
|
// First check if identity is valid
|
|
const isValid = await this.validIdentityGuard.exec(dataArg);
|
|
if (!isValid) {
|
|
return false;
|
|
}
|
|
|
|
// Check if user has admin role
|
|
return dataArg.identity.role === 'admin';
|
|
},
|
|
{
|
|
failedHint: 'user is not admin',
|
|
name: 'adminIdentityGuard',
|
|
}
|
|
);
|
|
} |