@levante-framework/firekit
Version:
A library to facilitate Firebase authentication and Firestore interaction for LEVANTE apps
548 lines (547 loc) • 25.6 kB
TypeScript
import { HttpsCallableResult } from 'firebase/functions';
import { AuthPersistence, MarkRawConfig } from './firestore/util';
import { Assessment, FirebaseProject, Name, OrgLists, RoarConfig, UserDataInAdminDb, Legal } from './interfaces';
import { UserInput } from './firestore/app/user';
import { RoarAppkit } from './firestore/app/appkit';
import { RoarTaskVariant, FirestoreVariantData, FirestoreTaskData, TaskVariantBase } from './firestore/app/task';
declare enum AuthProviderType {
GOOGLE = "google",
EMAIL = "email",
USERNAME = "username",
PASSWORD = "password"
}
interface CreateUserInput {
email: string;
password?: string;
activationCode?: string;
dob: string;
grade: string;
pid?: string;
ell_status?: boolean;
iep_status?: boolean;
frl_status?: boolean;
state_id?: string;
gender?: string;
hispanic_ethnicity?: string;
race?: string[];
home_language?: string[];
name?: {
first?: string;
middle?: string;
last?: string;
};
username?: string;
unenroll?: boolean;
schools: {
id: string;
} | null;
districts: {
id: string;
} | null;
classes: {
id: string;
} | null;
families: {
id: string;
} | null;
groups: {
id: string;
} | null;
}
export interface ChildData {
email: string;
password: string;
userData: CreateUserInput;
familyId: string;
orgCode: string;
}
interface CurrentAssignments {
assigned: string[];
started: string[];
completed: string[];
}
export interface RequestConfig {
headers: {
Authorization: string;
};
baseURL: string;
}
interface LevanteUserData {
id: string;
userType: string;
childId: string;
parentId: string;
teacherId: string;
month: string;
year: string;
group: string[];
}
interface LevanteSurveyResponses {
[key: string]: string;
}
interface UpdateTaskVariantData {
taskId: string;
data: FirestoreTaskData | FirestoreVariantData;
variantId?: string;
}
export interface Emulators {
auth: {
host: string;
port: number;
};
firestore: {
host: string;
port: number;
};
functions: {
host: string;
port: number;
};
ui: {
host: string;
port: number;
};
hub: {
host: string;
port: number;
};
logging: {
host: string;
port: number;
};
}
export declare class RoarFirekit {
admin?: FirebaseProject;
currentAssignments?: CurrentAssignments;
oAuthAccessToken?: string;
roarAppUserInfo?: UserInput;
roarConfig: RoarConfig;
emulatorConfig?: Emulators;
userData?: UserDataInAdminDb;
listenerUpdateCallback: (...args: unknown[]) => void;
private _admin?;
private _adminClaimsListener?;
private _adminOrgs?;
private _adminTokenListener?;
private _authPersistence;
private _identityProviderType?;
private _identityProviderId?;
private _idTokenReceived?;
private _idTokens;
private _initialized;
private _markRawConfig;
private _roarUid?;
private _superAdmin?;
private _verboseLogging?;
/**
* Create a RoarFirekit. This expects an object with keys `roarConfig`,
* where `roarConfig` is a [[RoarConfig]] object.
* @param {{roarConfig: RoarConfig }=} destructuredParam
* roarConfig: The ROAR firebase config object
*/
constructor({ roarConfig, verboseLogging, authPersistence, markRawConfig, listenerUpdateCallback, emulatorConfig, }: {
roarConfig: RoarConfig;
emulatorConfig?: Emulators;
dbPersistence: boolean;
authPersistence?: AuthPersistence;
markRawConfig?: MarkRawConfig;
verboseLogging: boolean;
listenerUpdateCallback?: (...args: unknown[]) => void;
});
private _getProviderIds;
private _scrubAuthProperties;
init(): Promise<this>;
private verboseLog;
get initialized(): boolean;
/**
* Verifies if the RoarFirekit instance has been initialized.
*
* This method checks if the RoarFirekit instance has been initialized by checking the `_initialized` property.
* If the instance has not been initialized, it throws an error with a descriptive message.
*
* @throws {Error} - If the RoarFirekit instance has not been initialized.
*
*/
private _verifyInit;
/**
* Verifies if the user is authenticated in the application.
*
* This method checks if the user is authenticated in both the admin and assessment Firebase projects.
* If the user is authenticated in both projects, the method returns without throwing an error.
* If the user is not authenticated in either project, the method throws an error with the message 'User is not authenticated.'
*
* @throws {Error} - Throws an error if the user is not authenticated.
*/
private _verifyAuthentication;
private _verifyAdmin;
/**
* Listens for changes in the user's custom claims and updates the internal state accordingly.
*
* This method sets up a snapshot listener on the user's custom claims document in the admin Firebase project.
* When the listener detects changes in the claims, it updates the internal state of the `RoarAuth` instance.
* It also refreshes the user's ID token if the claims have been updated.
*
* @param {FirebaseFirestore.Firestore} firekit.db - The Firestore database instance for the admin Firebase project.
* @param {FirebaseAuth.User} firekit.user - The user object for the admin Firebase project.
* @returns {FirebaseFirestore.Unsubscribe} - The unsubscribe function to stop listening for changes in the user's custom claims.
* @throws {FirebaseError} - If there is an error setting up the snapshot listener.
*/
private _listenToClaims;
/**
* Forces a refresh of the ID token for the admin Firebase user.
*
* This method retrieves the ID token for the admin Firebase user
* and refreshes it. It ensures that the token is up-to-date and valid.
*
* @returns {Promise<void>} - A promise that resolves when the ID tokens are refreshed successfully.
* @throws {FirebaseError} - If an error occurs while refreshing the ID tokens.
*/
forceIdTokenRefresh(): Promise<void>;
/**
* Listens for changes in the ID token of the specified Firebase project and updates the corresponding token.
*
* This method sets up a listener to track changes in the ID token of the specified Firebase project (admin).
* When the ID token changes, it retrieves the new ID token and updates the corresponding token in the `_idTokens` object.
* It also calls the `listenerUpdateCallback` function to notify any listeners of the token update.
*
* @param {FirebaseProject} firekit - The Firebase project to listen for token changes.
* @param {'admin'} _type - The type of Firebase project ('admin').
* @returns {firebase.Unsubscribe} - A function to unsubscribe from the listener.
* @private
*/
private _listenToTokenChange;
/**
* Sets the UID custom claims for the admin and assessment UIDs in the Firebase projects.
*
* This method is responsible for associating the admin and assessment UIDs in the Firebase projects.
* It calls the setUidClaims cloud function in the admin Firebase project.
* If the cloud function execution is successful, it refreshes the ID tokens for both projects.
*
* @returns {Promise<any>} - A promise that resolves with the result of the setUidClaims cloud function execution.
* @param {object} input - An object containing the required parameters
* @param {string} input.identityProviderId - The identity provider ID for the user (optional).
* @param {AuthProviderType} input.identityProviderType - The type of the identity provider (optional).
* @throws {Error} - If the setUidClaims cloud function execution fails, an Error is thrown.
*/
private _setUidCustomClaims;
/**
* Checks if the given email address is available for a new user registration.
*
* This method verifies if the given email address is not already associated with
* a user in the admin Firebase project. It returns a promise that resolves with
* a boolean value indicating whether the email address is available or not.
*
* @param {string} email - The email address to check.
* @returns {Promise<boolean>} - A promise that resolves with a boolean value indicating whether the email address is available or not.
* @throws {FirebaseError} - If an error occurs while checking the email availability.
*/
isEmailAvailable(email: string): Promise<boolean>;
/**
* Fetches the list of providers associated with the given user's email address.
*
* This method retrieves the list of providers associated with the given user's email address
* from the admin Firebase project. The list of providers includes the authentication methods
* that the user has used to sign in with their email address.
*
* @param {string} email - The email address of the user.
* @returns {Promise<string[]>} - A promise that resolves with an array of provider IDs.
* @throws {FirebaseError} - If an error occurs while fetching the email authentication methods.
*/
fetchEmailAuthMethods(email: string): Promise<string[]>;
/**
* Registers a new user with the provided email and password.
*
* This method creates a new user in both the admin and assessment Firebase projects.
* It first creates the user in the admin project and then in the assessment project.
* After successful user creation, it sets the UID custom claims by calling the `_setUidCustomClaims` method.
*
* @param {object} params - The parameters for registering a new user.
* @param {string} params.email - The email address of the new user.
* @param {string} params.password - The password of the new user.
* @returns {Promise<void>} - A promise that resolves when the user registration is complete.
* @throws {AuthError} - If the user registration fails, the promise will be rejected with an AuthError.
*/
registerWithEmailAndPassword({ email, password }: {
email: string;
password: string;
}): Promise<HttpsCallableResult<unknown>>;
/**
* Initiates a login process using an email and password.
*
* This method signs in the user with the provided email and password in both the admin and assessment Firebase projects.
* It first signs in the user in the admin project and then in the assessment project. After successful sign-in, it sets
* the UID custom claims by calling the `_setUidCustomClaims` method.
*
* @param {object} params - The parameters for initiating the login process.
* @param {string} params.email - The email address of the user.
* @param {string} params.password - The password of the user.
* @returns {Promise<void>} - A promise that resolves when the login process is complete.
* @throws {AuthError} - If the login process fails, the promise will be rejected with an AuthError.
*/
logInWithEmailAndPassword({ email, password }: {
email: string;
password: string;
}): Promise<HttpsCallableResult<unknown>>;
/**
* Link the current user with email and password credentials.
*
* This method creates a credential using the provided email and password, and then links the user's account with the current user in both the admin and app Firebase projects.
*
* @param {string} email - The email of the user to link.
* @param {string} password - The password of the user to link.
*
* @returns {Promise<void>} - A promise that resolves when the user is successfully linked with the specified authentication provider.
*/
linkEmailPasswordWithAuthProvider(email: string, password: string): Promise<import("@firebase/auth").UserCredential>;
/**
* Initiates the login process with an email link.
*
* This method sends a sign-in link to the specified email address. The user
* can click on the link to sign in to their account. The sign-in process is
* handled in a separate browser window or tab.
*
* @param {object} params - The parameters for initiating the login process.
* @param {string} params.email - The email address to send the sign-in link to.
* @param {string} params.redirectUrl - The URL to redirect the user to after they click on the sign-in link.
* @returns {Promise<void>} - A promise that resolves when the sign-in link is sent successfully.
*/
initiateLoginWithEmailLink({ email, redirectUrl }: {
email: string;
redirectUrl: string;
}): Promise<void>;
/**
* Check if the given email link is a sign-in with email link.
*
* This method checks if the given email link is a valid sign-in with email link
* for the admin Firebase project. It returns a promise that resolves with a boolean
* value indicating whether the email link is valid or not.
*
* @param {string} emailLink - The email link to check.
* @returns {Promise<boolean>} - A promise that resolves with a boolean value indicating whether the email link is valid or not.
*/
isSignInWithEmailLink(emailLink: string): Promise<boolean>;
signInWithEmailLink({ email, emailLink }: {
email: string;
emailLink: string;
}): Promise<HttpsCallableResult<unknown> | undefined>;
/**
* Handle the sign-in process in a popup window.
*
* This method handles the sign-in process in a popup window from from an
* external identity provider. It retrieves the user's credentials from the
* popup result and authenticates the user to the admin Firebase project
* using these credentials.
*
* The identity provider token is generally mean to be one-time use only.
* Because of this, the external identity provider's credential cannot be
* reused in the assessment project. To authenticate into the assessment
* project, we ask the admin Firebase project itself to mint a new credential
* for the assessment project. Thus, the external identity providers are used
* only in the admin Firebase project. And the admin Firebase project acts as
* an "external" identity provider for the assessment project.
*
* Therefore, the workflow for this method is as follows:
* 1. Authenticate into the external provider using a popup window.
* 2. Retrieve the external identity provider's credential from the popup result.
* 3. Authenticate into the admin Firebase project with this credential.
* 4. Generate a new "external" credential from the admin Firebase project.
* 5. Authenticate into the assessment Firebase project with the admin project's "external" credential.
* 6. Set UID custom claims by calling setUidCustomClaims().
*
* @param {AuthProviderType} provider - The authentication provider to use. It can be one of the following:
* - AuthProviderType.GOOGLE
*
* @returns {Promise<UserCredential | null>} - A promise that resolves with the user's credential or null.
*/
signInWithPopup(provider: AuthProviderType): Promise<HttpsCallableResult<unknown> | undefined>;
/**
* Link the current user with the specified authentication provider using a popup window.
*
* This method opens a popup window to allow the user to sign in with the specified authentication provider.
* It then links the user's account with the current user in both the admin and app Firebase projects.
*
* @param {AuthProviderType} provider - The authentication provider to link with. It can be one of the following:
* - AuthProviderType.GOOGLE
*
* @returns {Promise<void>} - A promise that resolves when the user is successfully linked with the specified authentication provider.
*
* @throws {Error} - If the specified provider is not one of the allowed providers, an error is thrown.
*/
linkAuthProviderWithPopup(provider: AuthProviderType): Promise<HttpsCallableResult<unknown> | undefined>;
/**
* Initiates a redirect sign-in flow with the specified authentication provider.
*
* This method triggers a redirect to the authentication provider's sign-in page.
* After the user successfully signs in, they will be redirected back to the application.
*
* If the linkToAuthenticatedUser parameter is set to true, an existing user
* must already be authenticated and the user's account will be linked with
* the new provider.
*
* @param {AuthProviderType} provider - The authentication provider to initiate the sign-in flow with.
* It can be one of the following: AuthProviderType.GOOGLE.
* @param {boolean} linkToAuthenticatedUser - Whether to link an authenticated user's account with the new provider.
*
* @returns {Promise<void>} - A promise that resolves when the redirect sign-in flow is initiated.
* @throws {Error} - If the specified provider is not one of the allowed providers, an error is thrown.
*/
initiateRedirect(provider: AuthProviderType, linkToAuthenticatedUser?: boolean): Promise<never>;
/**
* Handle the sign-in process from a redirect result.
*
* This method handles the sign-in process after a user has been redirected
* from an external identity provider. It retrieves the user's credentials
* from the redirect result and authenticates the user to the admin Firebase
* project using the credentials.
*
* The identity provider token is generally mean to be one-time use only.
* Because of this, the external identity provider's credential cannot be
* reused in the assessment project. To authenticate into the assessment
* project, we ask the admin Firebase project itself to mint a new credential
* for the assessment project. Thus, the external identity providers are used
* only in the admin Firebase project. And the admin Firebase project acts as
* an "external" identity provider for the assessment project.
*
* Therefore, the workflow for this method is as follows:
* 1. Get the redirect result from the admin Firebase project.
* 2. Retrieve the external identity provider's credential from the redirect result.
* 3. Authenticate into the admin Firebase project with this credential.
* 4. Generate a new "external" credential from the admin Firebase project.
* 5. Authenticate into the assessment Firebase project with the admin project's "external" credential.
* 6. Set UID custom claims by calling setUidCustomClaims().
*
* @param {() => void} enableCookiesCallback - A callback function to be invoked when the enable cookies error occurs.
* @returns {Promise<{ status: 'ok' } | null>} - A promise that resolves with an object containing the status 'ok' if the sign-in is successful,
* or resolves with null if the sign-in is not successful.
*/
signInFromRedirectResult(enableCookiesCallback: () => void): Promise<HttpsCallableResult<unknown> | null>;
/**
* Unlinks the specified authentication provider from the current user.
*
* This method only unlinks the specified provider from the user in the admin Firebase project.
* The roarProciderIds.ROAR_ADMIN_PROJECT provider is maintained in the assessment Firebase project.
*
* @param {AuthProviderType} provider - The authentication provider to unlink.
* It can be one of the following: AuthProviderType.GOOGLE
* @returns {Promise<void>} - A promise that resolves when the provider is unlinked.
* @throws {Error} - If the provided provider is not one of the allowed providers.
*/
unlinkAuthProvider(provider: AuthProviderType): Promise<import("@firebase/auth").User>;
/**
* Sign out the current user from both the assessment (aka app) Firebase project and the admin Firebase project.
*
* This method clears the authentication properties and signs out the user from both the app (aka assessment) and admin Firebase projects.
*
* @returns {Promise<void>} - A promise that resolves when the user is successfully signed out.
*/
signOut(): Promise<void>;
get superAdmin(): boolean | undefined;
get idTokenReceived(): boolean | undefined;
get idTokens(): {
admin?: string | undefined;
app?: string | undefined;
};
get restConfig(): {
admin: {
headers: {
Authorization: string;
};
};
};
get adminOrgs(): Record<string, string[]> | undefined;
get dbRefs(): {
admin: {
user: import("@firebase/firestore").DocumentReference<import("@firebase/firestore").DocumentData>;
assignments: import("@firebase/firestore").CollectionReference<import("@firebase/firestore").DocumentData>;
runs: import("@firebase/firestore").CollectionReference<import("@firebase/firestore").DocumentData>;
tasks: import("@firebase/firestore").CollectionReference<import("@firebase/firestore").DocumentData>;
};
} | undefined;
getTasksDictionary(): Promise<Record<string, object>>;
getAdministrations({ testData, restrictToOpenAdministrations, }: {
testData: boolean;
restrictToOpenAdministrations: boolean;
}): Promise<unknown>;
getLegalDoc(docName: string): Promise<{
text: string;
version: any;
} | null>;
updateConsentStatus(docName: string, consentVersion: string, params?: {}): Promise<void>;
get roarUid(): string | undefined;
getRoarUid(): Promise<string | undefined>;
startAssessment(administrationId: string, taskId: string, taskVersion: string, targetUid?: string): Promise<RoarAppkit>;
completeAssessment(administrationId: string, taskId: string, targetUid?: string): Promise<HttpsCallableResult<unknown>>;
/**
* Create or update an administration
*
* @param input input object
* @param input.name The administration name
* @param input.assessments The list of assessments for this administration
* @param input.dateOpen The start date for this administration
* @param input.dateClose The end date for this administration
* @param input.sequential Whether or not the assessments in this
* administration must be taken sequentially
* @param input.orgs The orgs assigned to this administration
* @param input.tags Metadata tags for this administration
* @param input.administrationId Optional ID of an existing administration. If
* provided, this method will update an
* existing administration.
*/
upsertAdministration({ name, publicName, assessments, dateOpen, dateClose, sequential, orgs, tags, administrationId, isTestData, legal, }: {
name: string;
publicName?: string;
assessments: Assessment[];
dateOpen: Date;
dateClose: Date;
sequential: boolean;
orgs: OrgLists;
tags: string[];
administrationId?: string;
isTestData: boolean;
legal: Legal;
}): Promise<unknown>;
/**
* Delete an administration
*
* @param administrationId The administration ID to delete
*/
deleteAdministration(administrationId: string): Promise<HttpsCallableResult<unknown>>;
/**
* Send a password reset email to the specified user's email address.
*
* This will reset the password in the admin Firebase project. The assessment
* Firebase project remains unchanged because we use the admin project's
* credentials to authenticate into the assessment project.
*
* @param {string} email - The email address of the user to send the password reset email to.
* @returns A promise that resolves when the password reset email is sent.
*/
sendPasswordResetEmail(email: string): Promise<void>;
createAdministrator(email: string, name: Name, targetOrgs: OrgLists, targetAdminOrgs: OrgLists, isTestData?: boolean): Promise<void>;
/**
* Upserts an organization in the database.
*
* @param orgData The organization data to upsert.
* @returns The upserted organization id.
*/
upsertOrg(orgData: {
id?: string;
type: 'districts' | 'schools' | 'classes' | 'groups';
[key: string]: unknown;
}): Promise<HttpsCallableResult<unknown>>;
registerTaskVariant({ taskId, taskName, taskDescription, taskImage, taskURL, gameConfig, variantName, variantParams, registered, }: TaskVariantBase): Promise<RoarTaskVariant>;
updateTaskOrVariant(updateData: UpdateTaskVariantData): Promise<void>;
createUsers(userData: LevanteUserData): Promise<HttpsCallableResult<unknown>>;
saveSurveyResponses(surveyResponses: LevanteSurveyResponses): Promise<HttpsCallableResult<unknown>>;
linkUsers(users: LevanteUserData[]): Promise<HttpsCallableResult<unknown>>;
editUsers(users: {
uid: string;
month: string;
year: string;
group: string;
district: string;
school: string;
class: string;
}[]): Promise<HttpsCallableResult<unknown>>;
}
export {};