Backend: - MagicPairingCode + MagicSession Prisma models - /api/magic/pair/create (6-digit code, 10min TTL, single-use) - /api/magic/pair/redeem (no auth, returns mgc_* token) - /api/magic/info (public DMG metadata) - requireUser() accepts mgc_* tokens Mac-App (RebreakMagic): - LoginView: 6-digit code input (OTP-style), real AppIcon, no signup - AuthService: signInWithPairingCode() replaces email/pw flow Native-App: - MagicSheet (TrueSheet) in Settings: download + code generator + linked Macs - AddMacSheet: subtle banner pointing to /settings - de/en locales
77 lines
1.9 KiB
TypeScript
77 lines
1.9 KiB
TypeScript
import { randomBytes } from 'crypto';
|
|
|
|
/**
|
|
* POST /api/magic/pair/redeem
|
|
*
|
|
* KEIN auth required — Mac-App hat noch keinen Token.
|
|
* Body: { code: "482913", label?: "MacBook Pro" }
|
|
*
|
|
* Tauscht einen 6-stelligen Pairing-Code (single-use, 10min TTL) gegen einen
|
|
* MagicSession-Token ("mgc_<48 char>"). Token wird in Mac-Keychain gespeichert
|
|
* und ersetzt Supabase-JWT für alle /api/magic/* Endpoints.
|
|
*/
|
|
export default defineEventHandler(async (event) => {
|
|
const body = await readBody(event);
|
|
const { code, label } = body as { code?: string; label?: string };
|
|
|
|
if (!code || !/^\d{6}$/.test(code)) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
message: 'code muss 6 Ziffern enthalten',
|
|
});
|
|
}
|
|
|
|
const db = usePrisma();
|
|
const pairingCode = await db.magicPairingCode.findUnique({
|
|
where: { code },
|
|
select: {
|
|
id: true,
|
|
userId: true,
|
|
expiresAt: true,
|
|
redeemedAt: true,
|
|
},
|
|
});
|
|
|
|
if (!pairingCode) {
|
|
throw createError({ statusCode: 404, message: 'Code ungültig' });
|
|
}
|
|
|
|
if (pairingCode.redeemedAt !== null) {
|
|
throw createError({ statusCode: 410, message: 'Code bereits verwendet' });
|
|
}
|
|
|
|
if (pairingCode.expiresAt < new Date()) {
|
|
throw createError({ statusCode: 410, message: 'Code abgelaufen' });
|
|
}
|
|
|
|
// Generiere Session-Token
|
|
const token = 'mgc_' + randomBytes(36).toString('base64url');
|
|
|
|
const session = await db.magicSession.create({
|
|
data: {
|
|
userId: pairingCode.userId,
|
|
token,
|
|
label: label?.trim() || null,
|
|
},
|
|
select: { id: true, createdAt: true },
|
|
});
|
|
|
|
// Code als redeemed markieren (single-use)
|
|
await db.magicPairingCode.update({
|
|
where: { id: pairingCode.id },
|
|
data: {
|
|
redeemedAt: new Date(),
|
|
sessionId: session.id,
|
|
},
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
token,
|
|
sessionId: session.id,
|
|
createdAt: session.createdAt.toISOString(),
|
|
},
|
|
};
|
|
});
|