/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see .
*/
import {jwtVerify, SignJWT} from 'jose';
import type {UserID} from '~/BrandedTypes';
import {Config} from '~/Config';
const SUDO_TOKEN_EXPIRY_SECONDS = 5 * 60;
export class SudoModeService {
private readonly secret: Uint8Array;
constructor() {
this.secret = new TextEncoder().encode(Config.auth.sudoModeSecret);
}
async generateSudoToken(userId: UserID): Promise {
const now = Math.floor(Date.now() / 1000);
const jwt = await new SignJWT({
type: 'sudo',
})
.setProtectedHeader({alg: 'HS256'})
.setSubject(userId.toString())
.setIssuedAt(now)
.setExpirationTime(now + SUDO_TOKEN_EXPIRY_SECONDS)
.sign(this.secret);
return jwt;
}
async verifySudoToken(token: string, userId: UserID): Promise {
try {
const {payload} = await jwtVerify(token, this.secret, {
algorithms: ['HS256'],
});
if (payload.type !== 'sudo') {
return false;
}
if (payload.sub !== userId.toString()) {
return false;
}
return true;
} catch {
return false;
}
}
}
let sudoModeServiceInstance: SudoModeService | null = null;
export function getSudoModeService(): SudoModeService {
if (!sudoModeServiceInstance) {
sudoModeServiceInstance = new SudoModeService();
}
return sudoModeServiceInstance;
}