feat(opsserver,web): replace the Angular UI and REST management layer with a TypedRequest-based ops server and bundled web frontend

This commit is contained in:
2026-03-20 16:43:44 +00:00
parent 0fc74ff995
commit d4f758ce0f
159 changed files with 12465 additions and 14861 deletions

View File

@@ -0,0 +1,263 @@
import * as plugins from '../../plugins.ts';
import * as interfaces from '../../../ts_interfaces/index.ts';
import type { OpsServer } from '../classes.opsserver.ts';
import { AuthService } from '../../services/auth.service.ts';
import { User } from '../../models/user.ts';
import { AuthProvider, PlatformSettings } from '../../models/index.ts';
export class AuthHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
private authService: AuthService;
constructor(private opsServerRef: OpsServer) {
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.authService = new AuthService();
}
/**
* Initialize auth handler - must be called after construction
*/
public async initialize(): Promise<void> {
this.registerHandlers();
}
private registerHandlers(): void {
// Login
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_Login>(
'login',
async (dataArg) => {
try {
const { email, password } = dataArg;
if (!email || !password) {
throw new plugins.typedrequest.TypedResponseError('Email and password are required');
}
const result = await this.authService.login(email, password);
if (!result.success || !result.user || !result.accessToken || !result.refreshToken) {
return {
errorCode: result.errorCode,
errorMessage: result.errorMessage,
};
}
const user = result.user;
const expiresAt = Date.now() + 15 * 60 * 1000; // 15 minutes
const identity: interfaces.data.IIdentity = {
jwt: result.accessToken,
refreshJwt: result.refreshToken,
userId: user.id,
email: user.email,
username: user.username,
displayName: user.displayName,
isSystemAdmin: user.isSystemAdmin,
expiresAt,
sessionId: result.sessionId!,
};
return {
identity,
user: {
id: user.id,
email: user.email,
username: user.username,
displayName: user.displayName,
avatarUrl: user.avatarUrl,
isSystemAdmin: user.isSystemAdmin,
isActive: user.isActive,
createdAt: user.createdAt instanceof Date
? user.createdAt.toISOString()
: String(user.createdAt),
lastLoginAt: user.lastLoginAt instanceof Date
? user.lastLoginAt.toISOString()
: user.lastLoginAt
? String(user.lastLoginAt)
: undefined,
},
};
} catch (error) {
if (error instanceof plugins.typedrequest.TypedResponseError) throw error;
throw new plugins.typedrequest.TypedResponseError('Login failed');
}
},
),
);
// Refresh Token
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RefreshToken>(
'refreshToken',
async (dataArg) => {
try {
if (!dataArg.identity?.refreshJwt) {
throw new plugins.typedrequest.TypedResponseError('Refresh token is required');
}
const result = await this.authService.refresh(dataArg.identity.refreshJwt);
if (!result.success || !result.user || !result.accessToken) {
throw new plugins.typedrequest.TypedResponseError(
result.errorMessage || 'Token refresh failed',
);
}
const user = result.user;
const expiresAt = Date.now() + 15 * 60 * 1000;
return {
identity: {
jwt: result.accessToken,
refreshJwt: dataArg.identity.refreshJwt,
userId: user.id,
email: user.email,
username: user.username,
displayName: user.displayName,
isSystemAdmin: user.isSystemAdmin,
expiresAt,
sessionId: result.sessionId || dataArg.identity.sessionId,
},
};
} catch (error) {
if (error instanceof plugins.typedrequest.TypedResponseError) throw error;
throw new plugins.typedrequest.TypedResponseError('Token refresh failed');
}
},
),
);
// Logout
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_Logout>(
'logout',
async (dataArg) => {
try {
if (!dataArg.identity?.jwt) {
throw new plugins.typedrequest.TypedResponseError('Identity required');
}
if (dataArg.all) {
const count = await this.authService.logoutAll(dataArg.identity.userId);
return { message: `Logged out from ${count} sessions` };
}
const sessionId = dataArg.sessionId || dataArg.identity.sessionId;
if (sessionId) {
await this.authService.logout(sessionId, {
userId: dataArg.identity.userId,
});
}
return { message: 'Logged out successfully' };
} catch (error) {
if (error instanceof plugins.typedrequest.TypedResponseError) throw error;
throw new plugins.typedrequest.TypedResponseError('Logout failed');
}
},
),
);
// Get Me
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetMe>(
'getMe',
async (dataArg) => {
try {
if (!dataArg.identity?.jwt) {
throw new plugins.typedrequest.TypedResponseError('Identity required');
}
const validated = await this.authService.validateAccessToken(dataArg.identity.jwt);
if (!validated) {
throw new plugins.typedrequest.TypedResponseError('Invalid or expired token');
}
const user = validated.user;
return {
user: {
id: user.id,
email: user.email,
username: user.username,
displayName: user.displayName,
avatarUrl: user.avatarUrl,
isSystemAdmin: user.isSystemAdmin,
isActive: user.isActive,
createdAt: user.createdAt instanceof Date
? user.createdAt.toISOString()
: String(user.createdAt),
lastLoginAt: user.lastLoginAt instanceof Date
? user.lastLoginAt.toISOString()
: user.lastLoginAt
? String(user.lastLoginAt)
: undefined,
},
};
} catch (error) {
if (error instanceof plugins.typedrequest.TypedResponseError) throw error;
throw new plugins.typedrequest.TypedResponseError('Failed to get user info');
}
},
),
);
// Get Auth Providers (public)
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetAuthProviders>(
'getAuthProviders',
async (_dataArg) => {
try {
const settings = await PlatformSettings.get();
const providers = await AuthProvider.getActiveProviders();
return {
providers: providers.map((p) => p.toPublicInfo()),
localAuthEnabled: settings.auth.localAuthEnabled,
defaultProviderId: settings.auth.defaultProviderId,
};
} catch (error) {
if (error instanceof plugins.typedrequest.TypedResponseError) throw error;
throw new plugins.typedrequest.TypedResponseError('Failed to get auth providers');
}
},
),
);
}
// Guard for valid identity - validates JWT via AuthService
public validIdentityGuard = new plugins.smartguard.Guard<{
identity: interfaces.data.IIdentity;
}>(
async (dataArg) => {
if (!dataArg.identity?.jwt) return false;
try {
const validated = await this.authService.validateAccessToken(dataArg.identity.jwt);
if (!validated) return false;
// Verify the userId matches the identity claim
if (dataArg.identity.userId !== validated.user.id) return false;
return true;
} catch {
return false;
}
},
{ failedHint: 'identity is not valid', name: 'validIdentityGuard' },
);
// Guard for admin identity - validates JWT + checks isSystemAdmin
public adminIdentityGuard = new plugins.smartguard.Guard<{
identity: interfaces.data.IIdentity;
}>(
async (dataArg) => {
const isValid = await this.validIdentityGuard.exec(dataArg);
if (!isValid) return false;
// Check isSystemAdmin claim from the identity
if (!dataArg.identity.isSystemAdmin) return false;
// Double-check from database
const user = await User.findById(dataArg.identity.userId);
if (!user || !user.isSystemAdmin) return false;
return true;
},
{ failedHint: 'user is not admin', name: 'adminIdentityGuard' },
);
}