feat(admin): integrate Next.js admin panel (admin-next/) from feat/nextjs-admin
- Add admin-next/: full Next.js + Prisma + shadcn admin panel (45 API routes, 76 components) - docker-compose: tg_shop_admin service (port 3000, shared db/shop.db, prisma), bot talks to it via ADMIN_CHAT_URL=http://tg_shop_admin:3000/api/chat - tor-proxy now proxies onion admin to tg_shop_admin:3000 (new panel) - chatbotService: ADMIN_CHAT_URL default = http://tg_shop_admin:3000/api/chat (removed localhost:3100 anachronism) - .env admin secrets gitignored (admin-next/.env)
This commit is contained in:
69
admin-next/src/lib/auth.ts
Executable file
69
admin-next/src/lib/auth.ts
Executable file
@@ -0,0 +1,69 @@
|
||||
import crypto from 'crypto';
|
||||
|
||||
const ADMIN_SECRET = process.env.ADMIN_SECRET || 'changeme';
|
||||
const SUPER_ADMIN_SECRET = process.env.SUPER_ADMIN_SECRET || '';
|
||||
|
||||
export type AdminRole = 'admin' | 'super_admin';
|
||||
|
||||
export interface AdminPayload {
|
||||
role: AdminRole;
|
||||
jti: string;
|
||||
iat: number;
|
||||
exp: number;
|
||||
}
|
||||
|
||||
export function createToken(secret: string): string {
|
||||
const isSuper = SUPER_ADMIN_SECRET && secret === SUPER_ADMIN_SECRET;
|
||||
const isAdmin = secret === ADMIN_SECRET;
|
||||
|
||||
if (!isAdmin && !isSuper) return '';
|
||||
|
||||
// If no super secret set, all admins are super
|
||||
const role: AdminRole = (!SUPER_ADMIN_SECRET || isSuper) ? 'super_admin' : 'admin';
|
||||
|
||||
const payload: AdminPayload = {
|
||||
role,
|
||||
jti: crypto.randomUUID(),
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
exp: Math.floor(Date.now() / 1000) + 86400,
|
||||
};
|
||||
|
||||
const payloadB64 = Buffer.from(JSON.stringify(payload)).toString('base64url');
|
||||
const signingKey = SUPER_ADMIN_SECRET || ADMIN_SECRET;
|
||||
const sig = crypto
|
||||
.createHmac('sha256', signingKey)
|
||||
.update(payloadB64)
|
||||
.digest('base64url');
|
||||
|
||||
return `${payloadB64}.${sig}`;
|
||||
}
|
||||
|
||||
export function verifyToken(token: string): AdminPayload | null {
|
||||
try {
|
||||
const [payloadB64, sig] = token.split('.');
|
||||
if (!payloadB64 || !sig) return null;
|
||||
|
||||
const signingKey = SUPER_ADMIN_SECRET || ADMIN_SECRET;
|
||||
const expected = crypto
|
||||
.createHmac('sha256', signingKey)
|
||||
.update(payloadB64)
|
||||
.digest('base64url');
|
||||
|
||||
if (sig !== expected) return null;
|
||||
|
||||
const payload: AdminPayload = JSON.parse(
|
||||
Buffer.from(payloadB64, 'base64url').toString()
|
||||
);
|
||||
|
||||
if (payload.exp < Math.floor(Date.now() / 1000)) return null;
|
||||
return payload;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function verifyReAuth(token: string): boolean {
|
||||
const isSuper = SUPER_ADMIN_SECRET && token === SUPER_ADMIN_SECRET;
|
||||
const isAdmin = token === ADMIN_SECRET;
|
||||
return isSuper || isAdmin;
|
||||
}
|
||||
Reference in New Issue
Block a user