@pedwise/next-firebase-auth-edge
Version:
Next.js 13 Firebase Authentication for Edge and server runtimes. Dedicated for Next 13 server components. Compatible with Next.js middleware.
227 lines • 10.3 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var _a, _b;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getFirebaseAuth = exports.handleExpiredToken = exports.isInvalidCredentialError = exports.isUserNotFoundError = exports.customTokenToIdAndRefreshTokens = void 0;
const firebase_1 = require("./firebase");
const token_verifier_1 = require("./token-verifier");
const error_1 = require("./error");
const auth_request_handler_1 = require("./auth-request-handler");
const credential_1 = require("./credential");
const user_record_1 = require("./user-record");
const token_generator_1 = require("./token-generator");
const runtime = __importStar(require("@edge-runtime/ponyfill"));
if ((typeof crypto === "undefined" || typeof global.crypto === "undefined") &&
Boolean((_a = runtime === null || runtime === void 0 ? void 0 : runtime.crypto) === null || _a === void 0 ? void 0 : _a.subtle)) {
global.crypto = runtime.crypto;
}
if ((typeof caches === "undefined" || typeof global.caches === "undefined") &&
Boolean((_b = runtime === null || runtime === void 0 ? void 0 : runtime.caches) === null || _b === void 0 ? void 0 : _b.open)) {
global.caches = runtime.caches;
}
const getCustomTokenEndpoint = (apiKey) => {
if ((0, firebase_1.useEmulator)()) {
return `http://${process.env
.FIREBASE_AUTH_EMULATOR_HOST}/identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${apiKey}`;
}
return `https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${apiKey}`;
};
const getRefreshTokenEndpoint = (apiKey) => {
if ((0, firebase_1.useEmulator)()) {
return `http://${process.env
.FIREBASE_AUTH_EMULATOR_HOST}/securetoken.googleapis.com/v1/token?key=${apiKey}`;
}
return `https://securetoken.googleapis.com/v1/token?key=${apiKey}`;
};
async function customTokenToIdAndRefreshTokens(customToken, firebaseApiKey) {
const refreshTokenResponse = await fetch(getCustomTokenEndpoint(firebaseApiKey), {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
token: customToken,
returnSecureToken: true,
}),
});
const refreshTokenJSON = (await refreshTokenResponse.json());
if (!refreshTokenResponse.ok) {
throw new Error(`Problem getting a refresh token: ${JSON.stringify(refreshTokenJSON)}`);
}
return {
idToken: refreshTokenJSON.idToken,
refreshToken: refreshTokenJSON.refreshToken,
};
}
exports.customTokenToIdAndRefreshTokens = customTokenToIdAndRefreshTokens;
const isUserNotFoundResponse = (data) => {
var _a, _b;
return (((_a = data === null || data === void 0 ? void 0 : data.error) === null || _a === void 0 ? void 0 : _a.code) === 400 &&
((_b = data === null || data === void 0 ? void 0 : data.error) === null || _b === void 0 ? void 0 : _b.message) === "USER_NOT_FOUND");
};
const refreshExpiredIdToken = async (refreshToken, apiKey) => {
// https://firebase.google.com/docs/reference/rest/auth/#section-refresh-token
const response = await fetch(getRefreshTokenEndpoint(apiKey), {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: `grant_type=refresh_token&refresh_token=${refreshToken}`,
cache: "no-store",
});
if (!response.ok) {
const data = await response.json();
const errorMessage = `Error fetching access token: ${JSON.stringify(data.error)} ${data.error_description ? `(${data.error_description})` : ""}`;
if (isUserNotFoundResponse(data)) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.USER_NOT_FOUND);
}
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.INVALID_CREDENTIAL, errorMessage);
}
const data = await response.json();
return data.id_token;
};
function isUserNotFoundError(error) {
return ((error === null || error === void 0 ? void 0 : error.code) ===
`auth/${error_1.AuthClientErrorCode.USER_NOT_FOUND.code}`);
}
exports.isUserNotFoundError = isUserNotFoundError;
function isInvalidCredentialError(error) {
return ((error === null || error === void 0 ? void 0 : error.code) ===
`auth/${error_1.AuthClientErrorCode.INVALID_CREDENTIAL.code}`);
}
exports.isInvalidCredentialError = isInvalidCredentialError;
async function handleExpiredToken(verifyIdToken, onExpired, onError) {
try {
return await verifyIdToken();
}
catch (e) {
// https://firebase.google.com/docs/reference/node/firebase.auth.Error
switch (e.code) {
case "auth/invalid-user-token":
case "auth/user-token-expired":
case "auth/user-disabled":
return onError(e);
case "auth/id-token-expired":
case "auth/argument-error":
try {
return await onExpired(e);
}
catch (e) {
return onError(e);
}
default:
return onError(e);
}
}
}
exports.handleExpiredToken = handleExpiredToken;
function getFirebaseAuth(serviceAccount, apiKey) {
const authRequestHandler = new auth_request_handler_1.AuthRequestHandler(serviceAccount);
const credential = new credential_1.ServiceAccountCredential(serviceAccount);
const tokenGenerator = (0, token_generator_1.createFirebaseTokenGenerator)(credential);
const handleTokenRefresh = async (refreshToken, firebaseApiKey) => {
const newToken = await refreshExpiredIdToken(refreshToken, firebaseApiKey);
const decodedToken = await verifyIdToken(newToken);
return {
decodedToken: decodedToken,
token: newToken,
};
};
async function getUser(uid) {
return authRequestHandler.getAccountInfoByUid(uid).then((response) => {
// Returns the user record populated with server response.
return new user_record_1.UserRecord(response.users[0]);
});
}
async function verifyDecodedJWTNotRevokedOrDisabled(decodedIdToken, revocationErrorInfo) {
// Get tokens valid after time for the corresponding user.
return getUser(decodedIdToken.sub).then((user) => {
if (user.disabled) {
throw new error_1.FirebaseAuthError(error_1.AuthClientErrorCode.USER_DISABLED, "The user record is disabled.");
}
// If no tokens valid after time available, token is not revoked.
if (user.tokensValidAfterTime) {
// Get the ID token authentication time and convert to milliseconds UTC.
const authTimeUtc = decodedIdToken.auth_time * 1000;
// Get user tokens valid after time in milliseconds UTC.
const validSinceUtc = new Date(user.tokensValidAfterTime).getTime();
// Check if authentication time is older than valid since time.
if (authTimeUtc < validSinceUtc) {
throw new error_1.FirebaseAuthError(revocationErrorInfo);
}
}
// All checks above passed. Return the decoded token.
return decodedIdToken;
});
}
async function verifyIdToken(idToken, checkRevoked = false) {
const isEmulator = (0, firebase_1.useEmulator)();
const idTokenVerifier = (0, token_verifier_1.createIdTokenVerifier)(serviceAccount.projectId);
const decodedIdToken = await idTokenVerifier.verifyJWT(idToken, isEmulator);
if (checkRevoked) {
return verifyDecodedJWTNotRevokedOrDisabled(decodedIdToken, error_1.AuthClientErrorCode.ID_TOKEN_REVOKED);
}
return decodedIdToken;
}
async function verifyAndRefreshExpiredIdToken(token, refreshToken) {
return await handleExpiredToken(async () => {
const decodedToken = await verifyIdToken(token);
return { token, decodedToken };
}, async () => {
if (refreshToken) {
return handleTokenRefresh(refreshToken, apiKey);
}
return null;
}, async () => {
return null;
});
}
function createCustomToken(uid, developerClaims) {
return tokenGenerator.createCustomToken(uid, developerClaims);
}
async function getCustomIdAndRefreshTokens(idToken, firebaseApiKey) {
const tenant = await verifyIdToken(idToken);
const customToken = await createCustomToken(tenant.uid);
return customTokenToIdAndRefreshTokens(customToken, firebaseApiKey);
}
async function deleteUser(uid) {
await authRequestHandler.deleteAccount(uid);
}
async function setCustomUserClaims(uid, customUserClaims) {
await authRequestHandler.setCustomUserClaims(uid, customUserClaims);
}
return {
verifyAndRefreshExpiredIdToken,
verifyIdToken,
createCustomToken,
getCustomIdAndRefreshTokens,
handleTokenRefresh,
deleteUser,
setCustomUserClaims,
getUser,
};
}
exports.getFirebaseAuth = getFirebaseAuth;
//# sourceMappingURL=index.js.map