next-firebase-auth-edge
Version:
Next.js Firebase Authentication for Edge and server runtimes. Compatible with latest Next.js features.
82 lines (81 loc) • 2.98 kB
JavaScript
import { getSdkVersion } from '../auth/auth-request-handler.js';
import { formatString } from '../auth/utils.js';
const FIREBASE_APP_CHECK_V1_API_URL_FORMAT = 'https://firebaseappcheck.googleapis.com/v1/projects/{projectId}/apps/{appId}:exchangeCustomToken';
const FIREBASE_APP_CHECK_CONFIG_HEADERS = {
'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`
};
export class AppCheckApiClient {
credential;
constructor(credential) {
this.credential = credential;
}
async exchangeToken(customToken, appId) {
const url = await this.getUrl(appId);
const token = await this.credential.getAccessToken(false);
const response = await fetch(url, {
method: 'POST',
headers: {
...FIREBASE_APP_CHECK_CONFIG_HEADERS,
Authorization: `Bearer ${token.accessToken}`
},
body: JSON.stringify({ customToken })
});
if (response.ok) {
return this.toAppCheckToken(response);
}
throw await this.toFirebaseError(response);
}
async getUrl(appId) {
const projectId = await this.credential.getProjectId();
const urlParams = {
projectId,
appId
};
const baseUrl = formatString(FIREBASE_APP_CHECK_V1_API_URL_FORMAT, urlParams);
return formatString(baseUrl);
}
async toFirebaseError(response) {
const data = (await response.json());
const error = data.error || {};
let code = 'unknown-error';
if (error.status && error.status in APP_CHECK_ERROR_CODE_MAPPING) {
code = APP_CHECK_ERROR_CODE_MAPPING[error.status];
}
const message = error.message || `Unknown server error: ${response.text}`;
return new FirebaseAppCheckError(code, message);
}
async toAppCheckToken(response) {
const data = await response.json();
const token = data.token;
const ttlMillis = this.stringToMilliseconds(data.ttl);
return {
token,
ttlMillis
};
}
stringToMilliseconds(duration) {
if (!duration.endsWith('s')) {
throw new FirebaseAppCheckError('invalid-argument', '`ttl` must be a valid duration string with the suffix `s`.');
}
const seconds = duration.slice(0, -1);
return Math.floor(Number(seconds) * 1000);
}
}
export const APP_CHECK_ERROR_CODE_MAPPING = {
ABORTED: 'aborted',
INVALID_ARGUMENT: 'invalid-argument',
INVALID_CREDENTIAL: 'invalid-credential',
INTERNAL: 'internal-error',
PERMISSION_DENIED: 'permission-denied',
UNAUTHENTICATED: 'unauthenticated',
NOT_FOUND: 'not-found',
UNKNOWN: 'unknown-error'
};
export class FirebaseAppCheckError extends Error {
code;
constructor(code, message) {
super(`(${code}): ${message}`);
this.code = code;
Object.setPrototypeOf(this, FirebaseAppCheckError.prototype);
}
}