Redesign Menu
This commit is contained in:
149
src/server/auth/service.ts
Normal file
149
src/server/auth/service.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import jwt from "jsonwebtoken";
|
||||
import {UserSession} from "@/types/auth";
|
||||
import {Claims} from "@/types/auth";
|
||||
|
||||
const DEBUG_TOKEN = (process.env.DEBUG_TOKEN ?? "true").toLowerCase() === "true";
|
||||
|
||||
function redactToken(t?: string) {
|
||||
if (!t) return "<empty>";
|
||||
const raw = t.startsWith("Bearer ") ? t.slice(7) : t;
|
||||
if (raw.length <= 12) return raw.replace(/./g, "*");
|
||||
return `${raw.slice(0, 6)}…${raw.slice(-6)}`;
|
||||
}
|
||||
|
||||
function parseBearer(input: string) {
|
||||
return input?.startsWith("Bearer ") ? input.slice(7) : input;
|
||||
}
|
||||
|
||||
function decodeBase64UrlPart(part: string): unknown {
|
||||
try {
|
||||
const pad =
|
||||
part.length % 4 === 2 ? "==" : part.length % 4 === 3 ? "=" : "";
|
||||
const s = part.replace(/-/g, "+").replace(/_/g, "/") + pad;
|
||||
const json = Buffer.from(s, "base64").toString("utf8");
|
||||
return JSON.parse(json);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUserFromToken(
|
||||
token: string,
|
||||
): Promise<UserSession | null> {
|
||||
const input = parseBearer(token);
|
||||
|
||||
if (DEBUG_TOKEN) {
|
||||
console.log("[server-auth] getUserFromToken() input:", {
|
||||
tokenPreview: redactToken(token),
|
||||
hasBearerPrefix: token?.startsWith?.("Bearer "),
|
||||
});
|
||||
}
|
||||
|
||||
if (!input) {
|
||||
if (DEBUG_TOKEN) console.warn("[server-auth] no token provided");
|
||||
return null;
|
||||
}
|
||||
|
||||
const HS_SECRET = process.env.JWT_SECRET;
|
||||
const RS_PUBLIC = process.env.JWT_PUBLIC_KEY;
|
||||
|
||||
let decoded: Claims | null = null;
|
||||
let headerAlg: string | undefined;
|
||||
|
||||
try {
|
||||
const [hPart] = input.split(".");
|
||||
const hdr = decodeBase64UrlPart(hPart) as { alg?: string } | null;
|
||||
headerAlg = hdr?.alg;
|
||||
|
||||
if (HS_SECRET) {
|
||||
decoded = jwt.verify(input, HS_SECRET, {
|
||||
algorithms: ["HS256", "HS384", "HS512"],
|
||||
clockTolerance: 5,
|
||||
}) as Claims;
|
||||
if (DEBUG_TOKEN)
|
||||
console.log("[server-auth] verified with HS secret", {alg: headerAlg});
|
||||
} else if (RS_PUBLIC) {
|
||||
decoded = jwt.verify(input, RS_PUBLIC, {
|
||||
algorithms: ["RS256", "RS384", "RS512"],
|
||||
clockTolerance: 5,
|
||||
}) as Claims;
|
||||
if (DEBUG_TOKEN)
|
||||
console.log("[server-auth] verified with RS public key", {
|
||||
alg: headerAlg,
|
||||
});
|
||||
} else {
|
||||
decoded = jwt.decode(input) as Claims | null;
|
||||
if (DEBUG_TOKEN)
|
||||
console.warn("[server-auth] decoded WITHOUT verification", {
|
||||
alg: headerAlg,
|
||||
});
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
const err = e as Error;
|
||||
if (DEBUG_TOKEN) {
|
||||
console.error("[server-auth] verify/decode failed:", {
|
||||
message: err.message,
|
||||
name: err.name,
|
||||
alg: headerAlg,
|
||||
});
|
||||
}
|
||||
try {
|
||||
const parts = input.split(".");
|
||||
if (parts.length >= 2) {
|
||||
decoded = decodeBase64UrlPart(parts[1]) as Claims | null;
|
||||
if (DEBUG_TOKEN)
|
||||
console.warn("[server-auth] manual payload decode fallback used");
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
if (!decoded) {
|
||||
if (DEBUG_TOKEN) console.warn("[server-auth] decoded = null");
|
||||
return null;
|
||||
}
|
||||
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
if (typeof decoded.nbf === "number" && now + 5 < decoded.nbf) {
|
||||
if (DEBUG_TOKEN)
|
||||
console.warn("[server-auth] token not yet valid", {now, nbf: decoded.nbf});
|
||||
}
|
||||
if (typeof decoded.exp === "number" && now - 5 > decoded.exp) {
|
||||
if (DEBUG_TOKEN)
|
||||
console.warn("[server-auth] token expired", {now, exp: decoded.exp});
|
||||
}
|
||||
|
||||
const user: UserSession = {
|
||||
id: decoded.sub ?? "",
|
||||
email: decoded.email,
|
||||
tenantId: decoded.tenant_id,
|
||||
tenantKey: decoded.tenant_key ?? decoded.tenantKey,
|
||||
roles: Array.isArray(decoded.roles) ? decoded.roles : [],
|
||||
_dbg: DEBUG_TOKEN
|
||||
? {
|
||||
alg: headerAlg,
|
||||
hasExp: typeof decoded.exp === "number",
|
||||
hasNbf: typeof decoded.nbf === "number",
|
||||
iss: decoded.iss,
|
||||
aud: decoded.aud,
|
||||
sid: decoded.sid,
|
||||
jti: decoded.jti,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
if (DEBUG_TOKEN) {
|
||||
console.log("[server-auth] user parsed:", {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
tenantId: user.tenantId,
|
||||
tenantKey: user.tenantKey,
|
||||
roles: user.roles,
|
||||
alg: user._dbg?.alg,
|
||||
});
|
||||
}
|
||||
|
||||
if (!user.id && DEBUG_TOKEN) console.warn("[server-auth] missing sub");
|
||||
return user;
|
||||
}
|
||||
Reference in New Issue
Block a user