diff --git a/package-lock.json b/package-lock.json index c9a1de8..60a2f69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1698,6 +1698,12 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "jsonc-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz", + "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==", + "dev": true + }, "just-extend": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", @@ -1754,12 +1760,24 @@ "yallist": "^4.0.0" } }, + "lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, "make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "marked": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/marked/-/marked-3.0.8.tgz", + "integrity": "sha512-0gVrAjo5m0VZSJb4rpL59K1unJAMb/hm8HRXqasD8VeC8m91ytDPMritgFSlKonfdt+rRYYpP/JfLxgIX8yoSw==", + "dev": true + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -2523,6 +2541,17 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "shiki": { + "version": "0.9.14", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.9.14.tgz", + "integrity": "sha512-uLHjjyJdNsMzF9GOF8vlOuZ8BwigiYPraMN5yjC826k8K7Xu90JQcC5GUNrzRibLgT2EOk9597I1IX+jRdA8nw==", + "dev": true, + "requires": { + "jsonc-parser": "^3.0.0", + "vscode-oniguruma": "^1.6.1", + "vscode-textmate": "5.2.0" + } + }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -2802,6 +2831,35 @@ "mime-types": "~2.1.24" } }, + "typedoc": { + "version": "0.22.10", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.10.tgz", + "integrity": "sha512-hQYZ4WtoMZ61wDC6w10kxA42+jclWngdmztNZsDvIz7BMJg7F2xnT+uYsUa7OluyKossdFj9E9Ye4QOZKTy8SA==", + "dev": true, + "requires": { + "glob": "^7.2.0", + "lunr": "^2.3.9", + "marked": "^3.0.8", + "minimatch": "^3.0.4", + "shiki": "^0.9.12" + }, + "dependencies": { + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, "typescript": { "version": "4.4.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", @@ -2869,6 +2927,18 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, + "vscode-oniguruma": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.1.tgz", + "integrity": "sha512-vc4WhSIaVpgJ0jJIejjYxPvURJavX6QG41vu0mGhqywMkQqulezEqEQ3cO3gc8GvcOpX6ycmKGqRoROEMBNXTQ==", + "dev": true + }, + "vscode-textmate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz", + "integrity": "sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==", + "dev": true + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 5ce8303..429a8cd 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "sinon-chai": "^3.5.0", "superagent": "^5.3.1", "ts-node": "^10.4.0", + "typedoc": "^0.22.10", "typescript": "^4.4.4" }, "husky": { diff --git a/src/mailer.ts b/src/mailer.ts index 2a601a1..e45c41f 100644 --- a/src/mailer.ts +++ b/src/mailer.ts @@ -8,21 +8,14 @@ export class Mailer { private config: Partial; private transporter: Mail; constructor(config: Partial) { - // Initialize the transport mechanism with nodemailer this.config = config; - const customTransport = config.mailer.transport; if (config.testMode?.noEmail) { this.transporter = nodemailer.createTransport( require('nodemailer-stub-transport')() ); - } else if (customTransport) { - this.transporter = nodemailer.createTransport( - customTransport(config.mailer.options) - ); } else { this.transporter = nodemailer.createTransport( - // @ts-ignore - config.mailer.options + config.mailer.transport ?? config.mailer.options ); } } diff --git a/src/types/config.ts b/src/types/config.ts index 4176200..d22373c 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -1,5 +1,11 @@ import { Sofa } from '@sl-nx/sofa-model'; +import { Transport } from 'nodemailer'; +import JSONTransport from 'nodemailer/lib/json-transport'; import Mail, { Address } from 'nodemailer/lib/mailer'; +import SendmailTransport from 'nodemailer/lib/sendmail-transport'; +import SESTransport from 'nodemailer/lib/ses-transport'; +import SMTPTransport from 'nodemailer/lib/smtp-transport'; +import StreamTransport from 'nodemailer/lib/stream-transport'; import { ConsentConfig } from './typings'; export interface TestConfig { @@ -13,6 +19,7 @@ export interface TestConfig { debugEmail?: boolean; } +/** Security/Session - related configuration */ export interface SecurityConfig { /** Roles given to a new user. Default: ['user'] */ defaultRoles: string[]; @@ -163,6 +170,10 @@ export interface DBServerConfig { designDocDir?: string; } +/** + * Configure templates that are sent out by superlogin automatically or + * on-demand when using `superlogin.sendEmail`. + */ export interface EmailTemplate { /** The subject for the sent out email */ subject: string; @@ -179,45 +190,32 @@ export interface EmailTemplate { template?: string; } -export interface MailOptions { - host?: string; - port?: number; - /** turns off STARTTLS support if true */ - ignoreTLS?: boolean | undefined; - /** forces the client to use STARTTLS. Returns an error if upgrading the connection is not possible or fails. */ - requireTLS?: boolean | undefined; - /** tries to use STARTTLS and continues normally if it fails */ - opportunisticTLS?: boolean | undefined; - secure?: boolean; - auth: { - user?: string; - pass?: string; - /** e.g. for sendGrid via `customTransport` */ - api_user?: string; - /** e.g. for sendGrid via `customTransport` */ - api_key?: string; - }; -} - +/** Configure how [nodemailer](https://nodemailer.com/about/) sends mails. */ export interface MailerConfig { /** Email address that all your system emails will be from */ fromEmail: string | Address; /** - * Use this if you want to specify a custom Nodemailer transport instead of - * passing SMTP. + * Use this if you want to pass an initialized `Transport` (Sendmail, SES,...) + * instead of using SMTP with the credentials provided in `options`. */ - transport?: any; + transport?: + | SendmailTransport + | StreamTransport + | JSONTransport + | SESTransport + | Transport; /** - * The options that will be passed into `createTransport`. If you do not use a - * custom transport, these are simply your SMTP credentials. + * If you do not use a custom `transport`, these are your SMTP credentials. * * See https://nodemailer.com/smtp/#examples for details. - * Type this as `any` if you pass custom options. */ - options?: MailOptions; + options?: SMTPTransport.Options; /** - * Additional message fields, see https://nodemailer.com/message/ - * Don't use it to pass `to`, `from`, `subject`, `html` and `text`. + * Additional message fields, e.g. `replyTo` and `cc`. + * Note that `to`, `from`, `subject`, `html` and `text` are expected to be + * handled by `superlogin` instead. + * + * See https://nodemailer.com/message/ for details. */ messageConfig?: Mail.Options; } @@ -329,9 +327,12 @@ export interface ProviderConfig { export interface Config { /** Only necessary for testing/debugging */ testMode?: Partial; + /** Security/Session - related configuration */ security?: Partial; local: Partial; + /** Configure the CouchDB server where all your databases are stored on */ dbServer: DBServerConfig; + /** Configure how mails are sent out to users */ mailer?: MailerConfig; /** * Customize the templates for the emails that SuperLogin sends out. @@ -343,16 +344,18 @@ export interface Config { * - `'modifiedPassword'` * - `'signupExistingEmail'` * - `'forgotUsername'` - * You can add additional templates and send them out via `sendEmail()` + * + * You can add additional templates and send them out via `sendEmail()`. */ emails?: Record; /** Custom settings to manage personal databases for your users */ userDBs?: UserDBConfig; + /** OAuth 2 providers */ providers?: { [provider: string]: ProviderConfig }; /** * Anything here will be merged with the default async userModel that * validates your local sign-up form. For details, check the - * [Sofa Model documentation](http://github.com/sl-nx/sofa-model) + * [Sofa Model README](http://github.com/sl-nx/sofa-model) */ userModel?: Sofa.Options | Sofa.AsyncOptions; } diff --git a/src/user.ts b/src/user.ts index a5b4028..e592802 100644 --- a/src/user.ts +++ b/src/user.ts @@ -59,7 +59,7 @@ export class User { private onLinkActions: SlAction[]; private hasher: Hashing; - passwordConstraints; + private passwordConstraints; /** * Checks that a username is valid and not in use. * Resolves with nothing if successful. @@ -77,9 +77,10 @@ export class User { v: Record ) => string | void; + /** @internal */ userModel: Sofa.AsyncOptions; - resetPasswordModel: Sofa.AsyncOptions; - changePasswordModel: Sofa.AsyncOptions; + private resetPasswordModel: Sofa.AsyncOptions; + private changePasswordModel: Sofa.AsyncOptions; constructor( protected config: Config, @@ -766,6 +767,13 @@ export class User { return user; } + /** + * 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` + */ public async changePasswordSecure(login: string, form, req?): Promise { req = req || {}; const ChangePasswordModel = Model(this.changePasswordModel); @@ -843,17 +851,19 @@ export class User { } /** - * Changes the password of a user + * 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` */ public async changePassword( user_uid: string, newPassword: string, - userDoc: SlUserDoc, - req: any + userDoc?: SlUserDoc, + req?: any ): Promise { req = req || {}; if (!userDoc) { diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 0000000..788b811 --- /dev/null +++ b/typedoc.json @@ -0,0 +1,9 @@ +{ + "entryPoints": [ + "src/index.ts", + "src/types/config.ts", + "src/types/typings.ts", + "node_modules/@sl-nx/sofa-model/index.d.ts" + ], + "out": "docs" +}