Add registration flow and improve admin post management
This commit is contained in:
33
lib/auth.ts
33
lib/auth.ts
@@ -3,6 +3,8 @@ import { NextRequest } from "next/server";
|
||||
|
||||
const COOKIE_NAME = "admin_session";
|
||||
const encoder = new TextEncoder();
|
||||
let cachedKey: CryptoKey | null = null;
|
||||
let cachedSecret: string | null = null;
|
||||
|
||||
function getSecret() {
|
||||
const secret = process.env.SESSION_SECRET;
|
||||
@@ -15,16 +17,32 @@ function getSecret() {
|
||||
export type SessionPayload = {
|
||||
role: "admin";
|
||||
iat: number;
|
||||
exp?: number;
|
||||
uid?: string;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
async function hmacSha256(data: string, secret: string): Promise<string> {
|
||||
const key = await crypto.subtle.importKey(
|
||||
export function getAdminName() {
|
||||
return process.env.ADMIN_NAME?.trim() || "Admin";
|
||||
}
|
||||
|
||||
async function getHmacKey(secret: string) {
|
||||
if (cachedKey && cachedSecret === secret) {
|
||||
return cachedKey;
|
||||
}
|
||||
cachedSecret = secret;
|
||||
cachedKey = await crypto.subtle.importKey(
|
||||
"raw",
|
||||
encoder.encode(secret),
|
||||
{ name: "HMAC", hash: "SHA-256" },
|
||||
false,
|
||||
["sign"]
|
||||
);
|
||||
return cachedKey;
|
||||
}
|
||||
|
||||
async function hmacSha256(data: string, secret: string): Promise<string> {
|
||||
const key = await getHmacKey(secret);
|
||||
const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(data));
|
||||
return Buffer.from(sig).toString("base64url");
|
||||
}
|
||||
@@ -45,6 +63,12 @@ export async function verifySession(token?: string): Promise<SessionPayload | nu
|
||||
if (check !== sig) return null;
|
||||
try {
|
||||
const payload = JSON.parse(Buffer.from(base, "base64url").toString());
|
||||
if (typeof payload?.exp !== "number") {
|
||||
return null;
|
||||
}
|
||||
if (Date.now() > payload.exp) {
|
||||
return null;
|
||||
}
|
||||
return payload;
|
||||
} catch {
|
||||
return null;
|
||||
@@ -57,11 +81,12 @@ export async function requireAdminFromRequest(req: NextRequest): Promise<boolean
|
||||
}
|
||||
|
||||
export function setAdminCookie(token: string) {
|
||||
const isProd = process.env.NODE_ENV === "production";
|
||||
cookies().set(COOKIE_NAME, token, {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
secure: true,
|
||||
maxAge: 60 * 60 * 24 * 30
|
||||
secure: isProd,
|
||||
maxAge: 60 * 60 * 24
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
23
lib/opc.ts
Normal file
23
lib/opc.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export const OPC_SIGNAL_VALUES = ["solo-feed"] as const;
|
||||
|
||||
export type OpcSignalValue = (typeof OPC_SIGNAL_VALUES)[number];
|
||||
|
||||
export const OPC_SIGNAL_LABELS: Record<OpcSignalValue, string> = {
|
||||
"solo-feed": "solo-feed"
|
||||
};
|
||||
|
||||
export const DEFAULT_OPC_SIGNAL: OpcSignalValue = "solo-feed";
|
||||
|
||||
const OPC_SIGNAL_SET = new Set<string>(OPC_SIGNAL_VALUES);
|
||||
|
||||
export function isOpcSignal(value?: string | null): value is OpcSignalValue {
|
||||
if (!value) return false;
|
||||
return OPC_SIGNAL_SET.has(value);
|
||||
}
|
||||
|
||||
export function getOpcSignalLabel(value?: string | null): string {
|
||||
if (value && OPC_SIGNAL_SET.has(value)) {
|
||||
return OPC_SIGNAL_LABELS[value as OpcSignalValue];
|
||||
}
|
||||
return OPC_SIGNAL_LABELS[DEFAULT_OPC_SIGNAL];
|
||||
}
|
||||
20
lib/password.ts
Normal file
20
lib/password.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import crypto from "crypto";
|
||||
|
||||
const ITERATIONS = 100_000;
|
||||
const KEY_LENGTH = 32;
|
||||
const DIGEST = "sha256";
|
||||
const SALT_BYTES = 16;
|
||||
|
||||
export function hashPassword(password: string, salt?: string) {
|
||||
const realSalt = salt ?? crypto.randomBytes(SALT_BYTES).toString("hex");
|
||||
const hash = crypto.pbkdf2Sync(password, realSalt, ITERATIONS, KEY_LENGTH, DIGEST).toString("hex");
|
||||
return { salt: realSalt, hash };
|
||||
}
|
||||
|
||||
export function verifyPassword(password: string, salt: string, hash: string) {
|
||||
const next = hashPassword(password, salt).hash;
|
||||
const a = Buffer.from(next, "hex");
|
||||
const b = Buffer.from(hash, "hex");
|
||||
if (a.length !== b.length) return false;
|
||||
return crypto.timingSafeEqual(a, b);
|
||||
}
|
||||
Reference in New Issue
Block a user