R61 A1-A6:Auth token refresh 主动续期
这个提交包含在:
+42
-1
@@ -159,6 +159,41 @@ function sanitizeReturnTo(value) {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function parseAccessTokenTtlSeconds(value) {
|
||||
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
||||
return Math.floor(value);
|
||||
}
|
||||
|
||||
if (typeof value !== "string") {
|
||||
throw new Error("Invalid access token TTL");
|
||||
}
|
||||
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
throw new Error("Invalid access token TTL");
|
||||
}
|
||||
|
||||
if (/^\d+$/.test(trimmed)) {
|
||||
return Number(trimmed);
|
||||
}
|
||||
|
||||
const match = trimmed.match(/^(\d+)\s*([smhd])$/i);
|
||||
if (!match) {
|
||||
throw new Error("Invalid access token TTL");
|
||||
}
|
||||
|
||||
const amount = Number(match[1]);
|
||||
const unit = match[2].toLowerCase();
|
||||
const secondsByUnit = {
|
||||
s: 1,
|
||||
m: 60,
|
||||
h: 60 * 60,
|
||||
d: 24 * 60 * 60,
|
||||
};
|
||||
|
||||
return amount * secondsByUnit[unit];
|
||||
}
|
||||
|
||||
async function authRoutes(fastify) {
|
||||
const loginBodySchema = z.object({
|
||||
identifier: z.string().min(3),
|
||||
@@ -174,6 +209,7 @@ async function authRoutes(fastify) {
|
||||
timeWindow: fastify.config.rateLimitLoginWindow,
|
||||
})
|
||||
: undefined;
|
||||
const accessTokenExpiresIn = parseAccessTokenTtlSeconds(fastify.config.accessTokenTtl);
|
||||
|
||||
fastify.post("/login", { preHandler: authRateLimit }, async (request, reply) => {
|
||||
const parsed = loginBodySchema.safeParse(request.body);
|
||||
@@ -388,6 +424,7 @@ async function authRoutes(fastify) {
|
||||
const accessToken = fastify.issueAccessToken(employee);
|
||||
return sendData(reply, request, {
|
||||
accessToken,
|
||||
expiresIn: accessTokenExpiresIn,
|
||||
mustChangePassword: Boolean(employee.must_change_password),
|
||||
});
|
||||
});
|
||||
@@ -572,7 +609,10 @@ async function authRoutes(fastify) {
|
||||
);
|
||||
|
||||
const accessToken = fastify.issueAccessToken(employee);
|
||||
return sendData(reply, request, { accessToken });
|
||||
return sendData(reply, request, {
|
||||
accessToken,
|
||||
expiresIn: accessTokenExpiresIn,
|
||||
});
|
||||
});
|
||||
|
||||
fastify.get("/sso/providers", async (request, reply) => {
|
||||
@@ -983,6 +1023,7 @@ async function authRoutes(fastify) {
|
||||
const accessToken = fastify.issueAccessToken(updatedEmployee);
|
||||
return sendData(reply, request, {
|
||||
accessToken,
|
||||
expiresIn: accessTokenExpiresIn,
|
||||
mustChangePassword: false,
|
||||
});
|
||||
});
|
||||
|
||||
在新工单中引用
屏蔽一个用户