feat: improve UI for timespan inputs on admin page (#726)

* Define Timestamp type

* Implement Timestamp utils

* Implement Timespan input

* Use timestamp input on config page

* Add timespan type to config services

* Refactor maxExpiration to use timespan type across services and components

* Update sessionDuration to use timespan type in config and adjust token expiration logic

* Update localized strings
This commit is contained in:
Aaron
2025-01-02 17:35:50 +01:00
committed by GitHub
parent df1ffaa2bc
commit 36afbf91b7
14 changed files with 158 additions and 20 deletions

View File

@@ -306,11 +306,12 @@ export class AuthService {
}
async createRefreshToken(userId: string, idToken?: string) {
const sessionDuration = this.config.get("general.sessionDuration");
const { id, token } = await this.prisma.refreshToken.create({
data: {
userId,
expiresAt: moment()
.add(this.config.get("general.sessionDuration"), "hours")
.add(sessionDuration.value, sessionDuration.unit)
.toDate(),
oauthIDToken: idToken,
},
@@ -341,14 +342,18 @@ export class AuthService {
secure: isSecure,
maxAge: 1000 * 60 * 60 * 24 * 30 * 3, // 3 months
});
if (refreshToken)
if (refreshToken) {
const now = moment();
const sessionDuration = this.config.get("general.sessionDuration");
const maxAge = moment(now).add(sessionDuration.value, sessionDuration.unit).diff(now);
response.cookie("refresh_token", refreshToken, {
path: "/api/auth/token",
httpOnly: true,
sameSite: "strict",
secure: isSecure,
maxAge: 1000 * 60 * 60 * this.config.get("general.sessionDuration"),
maxAge,
});
}
}
/**

View File

@@ -7,6 +7,7 @@ import {
import { Config } from "@prisma/client";
import { EventEmitter } from "events";
import { PrismaService } from "src/prisma/prisma.service";
import { stringToTimespan } from "src/utils/date.util";
/**
* ConfigService extends EventEmitter to allow listening for config updates,
@@ -35,6 +36,8 @@ export class ConfigService extends EventEmitter {
if (configVariable.type == "boolean") return value == "true";
if (configVariable.type == "string" || configVariable.type == "text")
return value;
if (configVariable.type == "timespan")
return stringToTimespan(value);
}
async getByCategory(category: string) {
@@ -94,7 +97,8 @@ export class ConfigService extends EventEmitter {
} else if (
typeof value != configVariable.type &&
typeof value == "string" &&
configVariable.type != "text"
configVariable.type != "text" &&
configVariable.type != "timespan"
) {
throw new BadRequestException(
`Config variable must be of type ${configVariable.type}`,
@@ -132,6 +136,7 @@ export class ConfigService extends EventEmitter {
condition: (value: number) => value >= 0 && value <= 9,
message: "Zip compression level must be between 0 and 9",
},
// TODO add validation for timespan type
];
const validation = validations.find((validation) => validation.key == key);

View File

@@ -56,12 +56,13 @@ export class ShareService {
const expiresNever = moment(0).toDate() == parsedExpiration;
const maxExpiration = this.config.get("share.maxExpiration");
if (
this.config.get("share.maxExpiration") !== 0 &&
maxExpiration.value !== 0 &&
(expiresNever ||
parsedExpiration >
moment()
.add(this.config.get("share.maxExpiration"), "hours")
.add(maxExpiration.value, maxExpiration.unit)
.toDate())
) {
throw new BadRequestException(

View File

@@ -10,3 +10,20 @@ export function parseRelativeDateToAbsolute(relativeDate: string) {
)
.toDate();
}
type Timespan = {
value: number;
unit: "minutes" | "hours" | "days" | "weeks" | "months" | "years";
};
export function stringToTimespan(value: string): Timespan {
const [time, unit] = value.split(" ");
return {
value: parseInt(time),
unit: unit as Timespan["unit"],
};
}
export function timespanToString(timespan: Timespan) {
return `${timespan.value} ${timespan.unit}`;
}