UNPKG

@perfood/couch-auth

Version:

Easy and secure authentication for CouchDB/Cloudant. Based on SuperLogin, updated and rewritten in Typescript.

264 lines (263 loc) 12.7 kB
/// <reference types="node" /> import { Sofa } from '@sl-nx/sofa-model'; import { EventEmitter } from 'events'; import { Request } from 'express'; import { DocumentScope, ServerScope } from 'nano'; import { Mailer } from './mailer'; import { Config } from './types/config'; import { ConsentRequest, CouchDbAuthDoc, CreateSessionOpts, HashResult, LocalHashObj, RegistrationForm, SlAction, SlLoginSession, SlRefreshSession, SlRequest, SlUserDoc } from './types/typings'; export declare enum ValidErr { 'exists' = "already in use", 'emailInvalid' = "invalid email", 'userInvalid' = "invalid username" } export declare class User { protected config: Config; userDB: DocumentScope<SlUserDoc>; couchAuthDB: DocumentScope<CouchDbAuthDoc>; protected mailer: Mailer; emitter: EventEmitter; protected couchServer: ServerScope; private dbAuth; private userDbManager; private session; private onCreateActions; private onLinkActions; private hasher; private passwordConstraints; /** * Checks that a username is valid and not in use. * Resolves with nothing if successful. * Resolves with an error object in failed. */ validateUsername: (v: string) => Promise<string | void>; /** * Checks that an email is valid and not in use. * Resolves with nothing if successful. * Resolves with an error object in failed. */ validateEmail: (v: string) => Promise<string | void>; /** Validates whether the _format_ matches the config */ private validateConsents; /** @internal */ userModel: Sofa.AsyncOptions; private resetPasswordModel; private changePasswordModel; constructor(config: Config, userDB: DocumentScope<SlUserDoc>, couchAuthDB: DocumentScope<CouchDbAuthDoc>, mailer: Mailer, emitter: EventEmitter, couchServer: ServerScope); /** * Hashes a password using PBKDF2 and returns an object containing `salt` and * `derived_key`. */ hashPassword(pw: string): Promise<HashResult>; /** * Verifies a password using a hash object. If you have a user doc, pass in * `local` as the hash object. * @returns resolves with `true` if valid, `false` if not */ verifyPassword(obj: LocalHashObj, pw: string): Promise<boolean>; /** * Use this to add as many functions as you want to transform the new user * document before it is saved. Your function should accept two arguments * (userDoc, provider) and return a Promise that resolves to the modified * user document. * onCreate functions will be chained in the order they were added. * @param {Function} fn */ onCreate(fn: SlAction): void; /** * Does the same thing as onCreate, but is called every time a user links a * new provider, or their profile information is refreshed. * This allows you to process profile information and, for example, create a * master profile. * If an object called profile exists inside the user doc it will be passed * to the client along with session information at each login. */ onLink(fn: SlAction): void; /** Validation function for ensuring that two fields match */ private matches; private processTransformations; /** * retrieves by email (default) or username or uuid if the config options are * set. Rejects if no valid format. */ getUser(login: string, allowUUID?: boolean): Promise<SlUserDoc | null>; private handleEmailExists; /** * Creates a new local user with a username/email and password. * @param form requires the following: `username` and/or `email`, `password`, * and `confirmPassword`. `name` is optional. Any additional fields must be * whitelisted in your config under `userModel` or they will be removed. * @param req additional request data passed to the email template * @returns `SlUserDoc` of the created user. Note that the `_rev` won't be * correct if `config.security.loginOnRegistration` is `false`: This is done * to prevent [time-based attacks](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#authentication-responses). * Send a response right after this function resolves and subscribe to the * `signup` event instead for further processing. */ createUser(form: RegistrationForm, req?: any): Promise<void | SlUserDoc>; private prepareNewUser; private insertNewUserDocument; /** * Creates a new user following authentication from an OAuth provider. * If the user already exists it will update the profile. * @param provider the name of the provider in lowercase, (e.g. 'facebook') * @param {any} auth credentials supplied by the provider * @param {any} profile the profile supplied by the provider */ createUserSocial(provider: string, auth: any, profile: any): Promise<SlUserDoc>; /** * like `createUserSocial`, but for an already existing user identified by * `login` */ linkUserSocial(login: string, provider: string, auth: any, profile: any): Promise<SlUserDoc>; /** * Removes the specified provider from the user's account. * `local` cannot be removed. If there is only one provider left it will fail. * Returns the modified user, if successful. * @param login email, username or UUID * @param provider the OAuth provider */ unlinkUserSocial(login: string, provider: string): Promise<SlUserDoc>; /** * Creates a new session for a user * @param params: The login options. * - `login`: the email, username or UUID (depending on your config) * - `provider`: 'local' or one of the configured OAuth providers * - `byUUID`: if `true`, interpret `login` always as UUID * - `sessionType`: see `security` -> `sessionConfig` for details * @returns the new session */ createSession(params: CreateSessionOpts): Promise<SlLoginSession>; /** * Extends the life of your current token and returns updated token information. * The only field that will change is expires. Expired sessions are removed. * todo: * - handle error if invalid state occurs that doc is not present. */ refreshSession(sessionId: string): Promise<SlRefreshSession>; /** * Required form fields: token, password, and confirmPassword */ resetPassword(form: any, req?: Partial<Request>): Promise<SlUserDoc>; /** * Changes the password of a user, validating the provided data. * @param login the `email`, `_id` or `key` of the `sl-user` to updated * @param form `newPassword`, `confirmPassword` (same) and `currentPassword` * as sent by the user. * @param req additional data that will be passed to the template as `req` */ changePasswordSecure(login: string, form: any, req?: any): Promise<void>; forgotUsername(email: string, req: Partial<Request>): Promise<void>; /** * Changes the password of a user. Note that this method does not perform * any validations of the supplied password as `changePasswordSecure` does. * @param user_uid the UUID of the user (without hypens, `_id` in `sl-users`) * @param newPassword the new password for the user * @param userDoc the `SlUserDoc` of the user. Will be retrieved by the * `user_uid` if not passed. * @param req additional data that will be passed to the template as `req` */ changePassword(user_uid: string, newPassword: string, userDoc?: SlUserDoc, req?: any): Promise<void>; private sendModifiedPasswordEmail; /** * sends out a passwort reset email, if the user exists * @param email email of the user * @param req additional request data, passed to the template as `req` */ forgotPassword(email: string, req: any): Promise<void>; private completeForgotPassRequest; /** * Marks the user's email as verified. `token` comes from the confirmation * email. Resolves if the verification is successful or the token was still * saved from a previous verification. Rejects if token is invalid. * @param token */ verifyEmail(token: string): Promise<void>; private markEmailAsVerified; private completeEmailChange; /** * Changes the user's email. If email verification is enabled * (`local.sendConfirmEmail`), a confirmation email will be sent out. * @param login user's email, username or UUID (depending on your config) * @param newEmail the new email * @param req additional request data, passed to the template as `req` */ changeEmail(login: string, newEmail: string, req: Partial<SlRequest>): Promise<void>; /** * Deauthorizes the specified database from the user's account, and optionally destroys it. * @param login email, username or UUID of the user (depending on your config) * @param dbName full path for a shared db, or base name for a private db * @param deletePrivate when true, will destroy a private DB with the `dbName` * @param deleteShared when true, will destroy a shared DB with the `dbName. * Caution: may destroy other users' data! */ removeUserDB(login: string, dbName: string, deletePrivate?: boolean, deleteShared?: boolean): Promise<import("nano").DocumentInsertResponse>; /** * Logs out all of a user's sessions. One of `login` or `session_id` must be * provided. * @param login the email, username or UUID of the user * @param session_id the id of the session - i.e. `org.couchdb.user:${suffix}` * @returns */ logoutAll(login: string, session_id: string): Promise<import("nano").DocumentInsertResponse>; /** * Logs out the specified session. Note that in case of a server error, it can * happen that only the entry in the `_users` DB is removed. This deauthorizes * the user, but the records in `sl-users` might not be accurate in that case. */ logoutSession(session_id: string): Promise<{}>; /** Logs out all of a user's sessions, except for the one specified. */ logoutOthers(sessionId: string): Promise<false | import("nano").DocumentInsertResponse>; /** * Removes a user and terminates all of his sessions * @param login the user's uuid or email/key * @param destroyDBs use `true` to also remove the personal DBs of the user * @param reason additional information to be emmitted by `'user-deleted'` */ removeUser(login: string, destroyDBs?: boolean, reason?: string): Promise<void>; /** * Confirms the user:password that has been passed as Bearer Token. Returns * the passed `key` + the `user_uid`, `expires`, `roles`, `provider` from the * the contents of the doc in the `_users`-DB. */ confirmSession(key: string, password: string): Promise<{ key: string; user_uid: string; expires: number; roles: string[]; provider: string; }>; /** * Associates a new database with the user's account. Will also authenticate * all existing sessions with the new database. If the optional fields are not * specified, they will be taken from `userDBs.model.{dbName}` or * `userDBs.model._default` in your config. * @param login the `key`, `email` or `_id` (user_uid) of the user * @param dbName the name of the database. For a shared db, this is the actual * path. For a private db userDBs.privatePrefix will be prepended, and * `${user_uid}` appended. * @param type 'private' (default) or 'shared' * @param designDocs the name of the designDoc (if any) that will be seeded. */ addUserDB(login: string, dbName: string, type?: 'private' | 'shared', designDocs?: any, partitioned?: any): Promise<import("nano").DocumentInsertResponse>; /** * Adds the private and authorises access to the shared userDBs */ private addUserDBs; /** * Deauthorizes all expired keys from the authentification-DB (`_users`) and * superlogin's db (`sl-users`). Call this regularily! */ removeExpiredKeys(): Promise<string[]>; /** * @internal * returns the latest entries of the current user's consents */ getCurrentConsents(login: string): Promise<Record<string, import("./types/typings").ConsentSlEntry>>; /** * @internal * validates and performs the update to the consents, returning the updated * consents if successful */ updateConsents(login: string, consentUpdate: Record<string, ConsentRequest>): Promise<Record<string, import("./types/typings").ConsentSlEntry>>; }