Clone
import { AuthError } from '../utils/errors';
export async function verifyAuth(request, env) {
// Skip auth for public endpoints
const url = new URL(request.url);
const publicPaths = ['/health', '/api/health', '/api/auth/request', '/api/auth/verify'];
// Skip auth for logo serving (public endpoint)
if (url.pathname.startsWith('/api/logo/get/')) {
return null;
}
// Skip auth for non-API paths (like redirects) or public API paths
if (!url.pathname.startsWith('/api') || publicPaths.includes(url.pathname)) {
return null;
}
// Get token from Authorization header
const authHeader = request.headers.get('Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
status: 401,
headers: { 'Content-Type': 'application/json' }
});
}
const token = authHeader.slice(7);
try {
// Verify JWT token
const payload = await verifyJWT(token, env.JWT_SECRET);
// Add user info to request
request.user = {
id: payload.sub,
email: payload.email
};
return null; // Continue to handler
} catch (error) {
return new Response(JSON.stringify({ error: 'Invalid token' }), {
status: 401,
headers: { 'Content-Type': 'application/json' }
});
}
}
export async function createJWT(payload, secret, expiresIn = '7d') {
const encoder = new TextEncoder();
const header = {
alg: 'HS256',
typ: 'JWT'
};
const now = Math.floor(Date.now() / 1000);
const exp = expiresIn === '7d' ? now + (7 * 24 * 60 * 60) : now + 3600;
const tokenPayload = {
...payload,
iat: now,
exp: exp
};
const encodedHeader = btoa(JSON.stringify(header)).replace(/=/g, '');
const encodedPayload = btoa(JSON.stringify(tokenPayload)).replace(/=/g, '');
const message = `${encodedHeader}.${encodedPayload}`;
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(message));
const encodedSignature = btoa(String.fromCharCode(...new Uint8Array(signature)))
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
return `${message}.${encodedSignature}`;
}
export async function verifyJWT(token, secret) {
const encoder = new TextEncoder();
const [header, payload, signature] = token.split('.');
if (!header || !payload || !signature) {
throw new Error('Invalid token format');
}
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['verify']
);
const message = `${header}.${payload}`;
const signatureBytes = Uint8Array.from(atob(signature.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0));
const valid = await crypto.subtle.verify(
'HMAC',
key,
signatureBytes,
encoder.encode(message)
);
if (!valid) {
throw new Error('Invalid signature');
}
const decodedPayload = JSON.parse(atob(payload));
// Check expiration
if (decodedPayload.exp && decodedPayload.exp < Math.floor(Date.now() / 1000)) {
throw new Error('Token expired');
}
return decodedPayload;
}