homelab-personal-cloud/core/src/http/mvc/login/identifier.controller.ts

88 lines
2.7 KiB
TypeScript

import { Controller, Get, Param, Post, Query, Res, UseInterceptors } from '@nestjs/common';
import { randomUUID } from 'node:crypto';
import { MVCResponse, Views } from '../mvc.types';
import { StateManagerService } from './state-manager.service';
import { Authentication } from '../../../domain/authentication.types';
import { Identity } from '../../../domain/identity.types';
import { LoginContextInterceptor } from './login-context.interceptor';
import { IdentityAuthDeviceDao } from '../../../persistence/identity-auth-device.dao';
import { Context } from './context.decorator';
@Controller({
version: '1',
path: 'auth/:realm/signin/identifier',
})
export class IdentifierController {
constructor(
private readonly stateManager: StateManagerService,
private readonly identityAuthDeviceDao: IdentityAuthDeviceDao,
) {}
@Get()
async getLogin(
@Param('realm') realm: string,
@Res() res: MVCResponse,
) {
const state = await this.stateManager.getNewState();
return res.render(Views.LoginView, {
realm,
state,
user_settings: {
theme: 'dark',
},
links: {
identifier_form: `/auth/${realm}/signin/identifier`
}
});
}
@Post()
@UseInterceptors(LoginContextInterceptor)
async postLogin(
@Param('realm') realm: string,
@Context() context: Authentication.Login.RequestContext,
@Res() res: MVCResponse,
) {
const devicesForUser = await this.identityAuthDeviceDao.findByRealmAndUsername(realm, context.username);
if (devicesForUser.length === 0) {
return res.render(Views.LoginPasswordChallenge, {
realm,
state: this.stateManager.updateState(context.state),
username: context.username,
user_settings: {
theme: 'dark',
},
links: {
select_device: null,
challenge_form: `/auth/${realm}/signin/challenge/${randomUUID()}`,
try_different_user: `/auth/${realm}/signin/identifier`,
}
});
}
const selectedDevice = devicesForUser.length === 1 ? devicesForUser[0] : devicesForUser.find(d => d.preferred);
if (selectedDevice?.deviceType === Identity.AuthDevice.Type.Password) {
return res.render(Views.LoginPasswordChallenge, {
realm,
state: this.stateManager.updateState(context.state),
username: context.username,
user_settings: {
theme: 'dark',
},
links: {
select_device: devicesForUser.length > 1 ? `/auth/${realm}/signin/challenge` : null,
challenge_form: `/auth/${realm}/signin/challenge/${selectedDevice.urn}`,
try_different_user: `/auth/${realm}/signin/identifier`,
}
});
}
}
}