next-firebase-auth-edge
Version:
Next.js Firebase Authentication for Edge and server runtimes. Compatible with latest Next.js features.
86 lines (85 loc) • 3.35 kB
JavaScript
import { decodeJwt } from 'jose';
import { AuthError, AuthErrorCode } from '../auth/error.js';
class ClientTokenCache {
cacheMap = {};
constructor() { }
get(value) {
if (!this.cacheMap[value]) {
return value;
}
return this.cacheMap[value];
}
set(originalValue, value) {
this.cacheMap = { [originalValue]: value };
}
}
const idTokenCache = new ClientTokenCache();
const customTokenCache = new ClientTokenCache();
export async function getValidIdToken({ serverIdToken, refreshTokenUrl, checkRevoked }) {
if (!serverIdToken) {
return null;
}
const token = idTokenCache.get(serverIdToken);
const payload = decodeJwt(token);
const exp = payload?.exp ?? 0;
if (!checkRevoked && exp > Date.now() / 1000) {
return token || serverIdToken;
}
const response = await fetchApi(refreshTokenUrl);
if (!response?.idToken) {
throw new AuthError(AuthErrorCode.INTERNAL_ERROR, 'Refresh token endpoint returned invalid response. This URL should point to endpoint exposed by the middleware and configured using refreshTokenPath option');
}
idTokenCache.set(serverIdToken, response.idToken);
return response.idToken;
}
export async function getValidCustomToken({ serverCustomToken, refreshTokenUrl, checkRevoked }) {
if (!serverCustomToken) {
return null;
}
const token = customTokenCache.get(serverCustomToken);
const payload = decodeJwt(token);
const exp = payload?.exp ?? 0;
if (!checkRevoked && exp > Date.now() / 1000) {
return token || serverCustomToken;
}
const response = await fetchApi(refreshTokenUrl);
if (!response) {
throw new AuthError(AuthErrorCode.INTERNAL_ERROR, 'Refresh token endpoint returned invalid response. This URL should point to endpoint exposed by the middleware and configured using refreshTokenPath option.');
}
if (!response.customToken) {
throw new AuthError(AuthErrorCode.INTERNAL_ERROR, 'Refresh token endpoint returned empty custom token. Make sure you have set `enableCustomToken` option to `true` in `authMiddleware`');
}
customTokenCache.set(serverCustomToken, response.customToken);
return response.customToken;
}
async function mapResponseToAuthError(response, input, init) {
if (response.status === 401) {
const data = await safeResponse(response);
return new AuthError(AuthErrorCode.INVALID_CREDENTIAL, data?.message);
}
const text = await safeResponse(response);
return new AuthError(AuthErrorCode.INTERNAL_ERROR, `next-firebase-auth-edge: Internal request to ${init?.method ?? 'GET'} ${input.toString()} has failed: ${text}`);
}
function safeResponse(response) {
const contentType = response.headers.get('content-type');
if (contentType && contentType.indexOf('application/json') !== -1) {
return response.json();
}
else {
return response.text();
}
}
async function fetchApi(input, init) {
const response = await fetch(input, {
...init,
headers: {
...init?.headers,
Accept: 'application/json',
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw await mapResponseToAuthError(response, input, init);
}
return safeResponse(response);
}