@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
TypeScript
/// <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>>;
}