feat(15-01): extend createSigningToken with signerEmail, add signer-download token pair
- createSigningToken now accepts optional signerEmail param and persists to DB - Added createSignerDownloadToken (72h TTL, purpose: signer-download) - Added verifySignerDownloadToken with purpose claim validation
This commit is contained in:
@@ -4,7 +4,7 @@ import { signingTokens } from '@/lib/db/schema';
|
|||||||
|
|
||||||
const getSecret = () => new TextEncoder().encode(process.env.SIGNING_JWT_SECRET!);
|
const getSecret = () => new TextEncoder().encode(process.env.SIGNING_JWT_SECRET!);
|
||||||
|
|
||||||
export async function createSigningToken(documentId: string): Promise<{ token: string; jti: string; expiresAt: Date }> {
|
export async function createSigningToken(documentId: string, signerEmail?: string): Promise<{ token: string; jti: string; expiresAt: Date }> {
|
||||||
const jti = crypto.randomUUID();
|
const jti = crypto.randomUUID();
|
||||||
const expiresAt = new Date(Date.now() + 72 * 60 * 60 * 1000); // 72 hours
|
const expiresAt = new Date(Date.now() + 72 * 60 * 60 * 1000); // 72 hours
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ export async function createSigningToken(documentId: string): Promise<{ token: s
|
|||||||
await db.insert(signingTokens).values({
|
await db.insert(signingTokens).values({
|
||||||
jti,
|
jti,
|
||||||
documentId,
|
documentId,
|
||||||
|
signerEmail: signerEmail ?? null,
|
||||||
expiresAt,
|
expiresAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -62,3 +63,19 @@ export async function verifyAgentDownloadToken(token: string): Promise<{ documen
|
|||||||
if (payload['purpose'] !== 'agent-download') throw new Error('Not an agent download token');
|
if (payload['purpose'] !== 'agent-download') throw new Error('Not an agent download token');
|
||||||
return { documentId: payload['documentId'] as string };
|
return { documentId: payload['documentId'] as string };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Signer download token — purpose: 'signer-download', 72h TTL, no DB record
|
||||||
|
// Sent to signers on completion so they can download the fully signed PDF.
|
||||||
|
export async function createSignerDownloadToken(documentId: string): Promise<string> {
|
||||||
|
return await new SignJWT({ documentId, purpose: 'signer-download' })
|
||||||
|
.setProtectedHeader({ alg: 'HS256' })
|
||||||
|
.setIssuedAt()
|
||||||
|
.setExpirationTime('72h')
|
||||||
|
.sign(getSecret());
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function verifySignerDownloadToken(token: string): Promise<{ documentId: string }> {
|
||||||
|
const { payload } = await jwtVerify(token, getSecret());
|
||||||
|
if (payload['purpose'] !== 'signer-download') throw new Error('Not a signer download token');
|
||||||
|
return { documentId: payload['documentId'] as string };
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user