7.7.0 • Published 2 months ago

nogin v7.7.0

Weekly downloads
3
License
MIT
Repository
github
Last release
2 months ago

npm Dependencies devDependencies

Tests badge Coverage badge

Known Vulnerabilities Total Alerts Code Quality: Javascript

Licenses badge

(Note that the more restrictive @fortawesome/fontawesome-free share-alike terms are for the fonts themselves, not its CSS (which is under MIT); see also licenses for dev. deps..)

issuehunt-to-marktext

Nogin

A maintained and expanded fork of https://github.com/braitsch/node-login.

nogin

The project name is a portmanteau of "Node" and "login" and is pronounced "noggin" (a colloquial word for "head").

So if you want Node login, use your "nogin"!

nogin

A basic account management system built in Node.js with the following features:

  • New user account creation
  • Email verification/activation
  • Secure password reset via email
  • Ability to update / delete account
  • Session tracking for logged-in users
  • Local cookie storage for returning Users
  • PBKDF2-based password encryption
  • XSRF/CSRF protection
  • helmet integration for HTTP headers control
  • sameSite session cookies
  • Timing safe login check comparisons
  • Throrough internationalization (i18n) and fundamental accessibility (a11y)
  • CLI option for managing accounts
  • 100% Cypress UI and CLI Testing Coverage
  • Tested on Chrome and Firefox but with a Babel/core-js Rollup routine that should allow the code to work in other browsers as well.

Improvements over node-login

While you can see CHANGES (or the 1.0.0 migration guide) to see all of the fixes and enhancements (including security fixes), over the excellent node-login package, the essential change has been to avoid the necessity of directly modifying source. This component has been retooled to allow it to be added as an npm dependency and provided command-line arguments which customize the appearance and behavior to a high degree—and using a config file or CLI flags rather than environmental variables.

You provide the app (through the --router argument) your Express app entry file and optionally other arguments. See the next section.

Besides these changes, there have been subsequent additions as well:

  • Groups, privilege, and user management
  • Additional security

Installation & Setup

  1. Install Node.js (minimum version of 10.4.0) and MongoDB if you haven't already. (Note that while we have provided a generic database adapter that could in theory be used to support other databases, MongoDB is the only currently supported database.)

  2. Install the package.

npm install nogin
  1. Install the peerDependencies. I recommend installing install-peerdeps (npm i -g install-peerdeps); then install-peerdeps nogin will auto-install the rest.

  2. Add a nogin.js file (see the nogin-sample.js file)

  3. Add a db directory at your project root (and probably add it to .gitignore and .npmignore)

  4. Use run-p (which is made available by the nogin dependency, npm-run-all) along with two of your own package.json scripts, one of which to start the mongo database, and the other to start the nogin server (note that nodemon is also provided as a dependency, so you could add that to the nogin call to watch for changes to nogin files):

{
    "scripts": {
        "mongo": "mongod --port=27017 --dbpath=db --bind_ip=127.0.0.1",
        "server": "nogin --localScripts --config nogin.js",
        "start": "run-p -r mongo server"
    }
}

(The -r flag indicates that an error in one script will lead both to exit.)

Alternatively, you can start MongoDB in its own separate shell:

mongod

...and from within the nogin directory in another terminal, start the server:

nogin --localScripts --config nogin.js

You will most likely also want to use the --router and possibly --fallback, as well as --postLoginRedirectPath / arguments. See these options for more details.

  1. Once the script mentions it is listening (on port 3000 by default), open a browser window and navigate to: http://127.0.0.1:3000/login

  2. (For your live site, ensure your nogin.js NL_SITE_URL is pointing to this port.)

  3. Set yourself as rootUser in nogin.js and add groups and privileges and assign groups to users as relevant. Use dots as namespaces in privilege and group names. Note that the built-in groups nogin.loggedInUsers and nogin.guests will apply to all who are logged in or not, respectfully. The privilegs should be additive from the level of guest to logged in user to regular user to root user.

  4. Check the privileges in your app. You can check the privileges from req.hasPrivilege(...) regardless of whether the user is logged in or a guest. You can also get the privileges from /_privs JavaScript. If you want live results, you can make a GET request to /_privs?format=json (or just import the helper nogin/hasPrivilege.js).

Steps for getting port that may block Mongo DB

MongoDB may end up with a process that interferes with starting a new instance.

On the Mac, you can follow these steps to resolve:

  1. Get the port sudo lsof -i :27017
  2. Then kill by kill PID with PID as the result of step 1 (or if necessary kill -2 PID).

Command line usage

cli.svg

To view as non-embedded HTML or SVG files (for copy-pasteable commands):

Command line summary

Server flags (without verbs)

When no verbs are added (only flags are supplied), you can see the above for a detailed description of the available flags. We group and summarize these here (all strings unless indicated). See also "Flags available regardless of "verb"" as those also apply as server flags.

Use in place of command line flags (CLI flags will take precedence)**
  • -c/--config - (Defaults to <cwd>/nogin.json; may also be a JS file.)
  • --cwd (Defaults to process.cwd().)
Required fields (no defaults)

You can also look at nogin-sample.js for how to set these values within your own nogin.js config file.

  • --secret
  • --NL_EMAIL_USER
  • --NL_EMAIL_PASS
  • --NL_EMAIL_HOST
  • --NL_EMAIL_FROM
  • --NL_SITE_URL
  • --fromText
  • --fromURL
Security
  • --disableXSRF (Boolean; defaults to false.)
  • --noHelmet (Boolean; defaults to false.)
  • --noHostValidation (Boolean; defaults to false.)
  • --RATE_LIMIT (A number defaulting to 700 for a rate limit.)
  • --transferLimit (A string like "50mb" to be passed to express.json() and express.urlencoded())
  • --csurfOptions (A string, or, in config, an object; defaults to {cookie: {signed: true, sameSite: "lax"}; note that if you are on HTTPS, it is recommended to set this to {cookie: {secure: true, signed: true, sameSite: "lax"}).
  • --helmetOptions (A string, or, in config, an object; defaults to {frameguard: {action: "SAMEORIGIN"}}). Note that SAMEORIGIN is required as the action to allow nogin to be used within your site's iframes.
  • --sessionOptions (A string, or, in config, an object; defaults to {resave: true, saveUninitialized: true} along with cookie: sessionCookieOptions, secret, and store: MongoStore.create({mongoUrl: DB_URL})
  • --sessionCookieOptions (A string, or, in config, an object; defaults to {sameSite: 'lax'})
Tweaks for general administration
  • --NS_EMAIL_TIMEOUT (number of milliseconds, defaulting to 5000)
  • --PORT (number, defaulting to 3000)
  • -a/--adapter (Defaults to "mongodb", the only current option.)
  • --rootUser - Users who are granted all available privileges to view and edit groups, privileges, and users
Tweaks for user-facing behavior
  • --requireName (Default is false.)
  • --countryCodes (Two-letter country codes as JSON array; defaults to codes in /app/server/modules/country-codes.json.)
Customizing locales
  • --localesBasePath (Defaults to app/server; use if need to redefine locale values.)
Customizing HTML

These should normally not changing, but can be changed to tweak the HTML that is rendered in emails or on the server.

  • --composeResetPasswordEmailView (Defaults to /app/server/views/composeResetPasswordEmail.js)
  • --composeActivationEmailView (Defaults to /app/server/views/composeActivationEmail.js)
  • --injectHTML (No extra HTML is injected by default)
  • --favicon (String path; defaults to blank.)
Customizing stylesheets
  • --stylesheet (String path; defaults to no extra stylesheets being used.)
  • --noBuiltinStylesheets (Boolean, defaults to false)
Customizing JavaScript
  • --localScripts (Boolean, defaults to false)
  • --userJS (None by default)
  • --userJSModule (None by default)

These should primarily only be used with testing:

  • --useESM (Boolean, defaults to false.)
  • --noPolyfill (Boolean, defaults to false.)
Customizing routes

This is for changing the names or behavior of existing routes. See "Adding routes" for supporting additional routes.

  • --postLoginRedirectPath (Path/URL to which to redirect after login; defaults to home (/home) (or locale equivalent), but you should probably set it so that it redirects instead to your root (/).
  • --customRoute (Multiple strings in format <locale>=<route>=<path>)
  • --crossDomainJSRedirects (Boolean, defaults to false.)
Adding routes
  • -s/--SERVE_COVERAGE (Boolean; defaults to false.)
  • --staticDir (One or more string paths)
  • --middleware (One or more middleware to be required)
  • --fallback - If you need a default static file server and not just specific paths, you can add your own file such as follows, adding any desired headers, etc.:
import express from 'express';

/**
 * @param {import('express').Request} req
 * @param {import('express').Response} res
 * @param {import('express').NextFunction} next
 * @returns {void}
 */
export default function fallback (req, res, next) {
  express.static('.', {
    setHeaders (resp /* , path, stat */) {
      resp.set({
        'Content-Security-Policy': `object-src 'none';`
      });
    }
  })(req, res, next);
}
  • --router - This is where your own (Express) app should be. Note: The following paths should not be set as they are reserved by nogin:
    • POST: /login, /logout, /home, /signup, /accessAPI, /reset-password, /lost-password, /delete, /reset
    • GET: /login, /logout, /home, /signup, /accessAPI, /reset-password, /activation, /users, /coverage, /groups, /privileges, _lang, _privs
/**
 * @param {import('express').Application} app
 * @param {{userJS: string}} opts
 * @returns {void}
 */
export default function router (app, opts) {
  // Grab your `nogin.js` options here as needed
  console.log('Started with options:', {
    ...opts,
    secret: '<HIDDEN>',
    NL_EMAIL_PASS: '<HIDDEN>'
  });

  app.get('/', (req, _res, next) => {
    // To see your user session info (and determine privileges), you can get
    //   them from `req.sesssion?.user`:
    console.log('req.session', {
      ...req.session?.user,
      _id: '<some unique ID>',
      name: 'Brett Zamir',
      email: '<the user email address>',
      user: 'brettz9',
      pass: '<the password hash>',
      country: 'US',
      activationCode: '<activation code hash>',
      activated: true,
      passVer: 1,
      date: 1725865509592,
      cookie: '<cookie hash>',
      ip: '::ffff:127.0.0.1'
    });

    // We just use express' approach to point `/` to the path `/index.html`.
    req.url = '/index.html';

    // Or instead add optional config to conditionally point to any
    //    Cypress-instrumented entry file:
    // req.url = instrumented
    //   ? '/instrumented/index.html'
    //   : '/index.html';

    // Or point the user to the login page if they are not yet logged in
    // req.url = req.session?.user
    //   ? '/index.html'
    //   : '/login';
    next();
  });
}
Used mainly for internal testing of nogin
  • -d/--JS_DIR - (Defaults to /app/public; used for pointing to instrumented path.)

Verbs

One can also add a verb to nogin (e.g., nogin read) which performs a different behavior from creating a server.

  • help - Use with one of the verbs below to get help for that command
  • read/view - View user account(s)
  • update - Update user account(s)
  • add/create - Create new user account(s)
  • remove/delete - Remove user account(s)
  • listIndexes - List indexes of the nogin database

Flags available regardless of "verb" (besides help)

Defaults in parentheses:

  • --loggerLocale ("en-US")
  • --noLogging (false)
  • -n/--DB_NAME ("nogin")
  • -t/--DB_HOST ("127.0.0.1")
  • -p/--DB_PORT (27017)
  • -u/--DB_USER
  • -x/--DB_PASS

Flags reused among verbs "read"/"view", "update", "add"/"create", "remove"/"delete".

These are are all multiple (one to be added for each user being viewed/added/etc.).

Unless notes, all types are strings.

  • --user (as the default option, the flag --user can be omitted)
  • --name
  • --email
  • --country (Two digit recognized country code)
  • --pass
  • --passVer (A number indicating the current schema version; should always be set to "1" currently.)
  • --date (A number timestamp for record creation date)
  • --activated (A boolean)

These are shared but will primarily only be of interest in internal nogin testing:

  • --activationCode
  • --unactivatedEmail
  • --activationRequestDate (A number timestamp)

Flags for specific verbs

add/create

Note that the CLI API for the add and update verbs does not currently perform all validation that the UI does, so you will need to have some familiarity with nogin internals to be sure to include all required fields.

  • --userFile - Path to JSON file containing data to populate
  • --cwd - Used with userFile
remove/delete
  • --all - Boolean to indicate desire to remove all user records!

Programmatic docs

  1. pnpm i (local install to get devDeps)
  2. pnpm build-docs
  3. Open docs/jsdoc/index.html

Contributing

Questions and suggestions for improvement are welcome.

For developing docs, see DEVELOPING.

To-dos

  1. Review csurf and usage
  2. Recheck coverage tests
  3. See about removing @fortawesome/fontawesome-free dependency (and if so, rebuild license badges and remove note above about its license)
  4. Login page
    1. Provide option for integration within an existing page to avoid need for separate login page (Ajax)
      1. Adapt server-side redirect functionality to give Ajax feedback to client so it could instead handle forwarding with a hash.
      2. Consider possibility of an option to merge with signup page
    2. Multiple simultaneously-shown login choices (e.g., to use version control or Cloud Storage and a database managing users and privileges)?
    3. WebSockets with express-ws and jquery-form?
    4. Optional captcha (see signup below)
  5. Signup/Home pages
    1. Allow Multiple choices
    2. Allow adding to "Set up new account" fields (based on a schema?) (to be injected into app/server/views/account.js) to be passed to the server (app/server/routeList.js) and saved in the database along with other fields (check the user-supplied don't overwrite built-ins) and shown on home (also built by account.js) (unless hidden?); not trusting the client-side values of course (could parse server-side-supplied schema for expected types); use json-editor?
      1. Plugin system to allow asking for and saving additional data per user. May optionally be able to reject submission, but should instead use an authentication strategy plugin, if it is more fundamental to authentication (since this should generally be safely additive).
      2. Concept of SharedStorage for grabbing local user data (or on a URL) for populating site profiles/preferences (under control of user, so they can manage their data in one place, and let sites update their own copy (or directly utilize the local copy) when online and checking)
    3. Captchas (svg-captcha (doesn't use easily breakable SVG text, and could convert to image))
      1. Plugin system for captchas, while potentially allowing saving to and retrieving from database (e.g., for admin-added list of Q&A's), need not be aware of any other co-submitted data like username, password, etc. Unlike "set up new account" plugins, wouldn't need access to user database, but can of course have potential to reject submission.
  6. Authentication strategies
    1. Passkeys
    2. See about passport-next integration
      1. WebSockets with passport?
      2. Supporting user choice of authentication method
        1. Different strategies could optionally offer different schemas for:
          1. User (Registration and edit user)
          2. Preferences (Tied to user but unlike general content, may possibly of interest across site/application such as desire for dark mode, and unlike most content, would be private and not likely of interest in content queries)
          3. Login (e.g., to auto-add field for captcha, or CryptoKey and CryptoKeyPair) generation/selection.
          4. Privileges, Privilege Groups, User Groups
    3. BrowserID to use with a server-side validation
      1. See https://github.com/jaredhanson/passport-browserid.
      2. Would presumably need to revive as a browser add-on
      3. Browser add-on could also expose global locally stored preferences
    4. Strategy idea: Check host of email domain and insist on .name at a reliable host which promises not to give out domains under a minimum fee (as a deterrent for spamming and encouragement for mail address portability); would need to find hosts willing to commit to such a policy.
    5. Add passwordless option
      1. See http://www.passportjs.org/packages/passport-passwordless/.
  7. Users page
    1. Ajax pagination
    2. Privileges
      1. Role-based privileges (esp. for reset/delete!) with admin screens
      2. Hierarchical groups and roles?
      3. Multiple group membership allowing multiple roles per group, including user-customizable roles in addition to built-in ones such as the "login" privilege; roles per user (without group)
        1. Make a simple version of groups where groups are auto-created that map to privileges (e.g., a login group), so can easily add a user to a login group rather than needing to first create the group and add the privilege to that group (these could be the built-in groups, along with a few combined ones like visitor/user/admin/superadmin)
      4. Anticipate privileges that come automatically based on events (e.g., as with StackExchange)
      5. Atomic privileges (e.g., view-users as well as more encompassing view privilege)
      6. IP addressed-based privileges (or exclusions)
      7. Expiring privileges (or tied into payment subscription or some other event, auto-renewing or not)
      8. See to-dos in code for methods needing these!
        1. Restore reset from GET page to POST on the user (admin) page.
      9. Use within authentication
      10. Tie into PaymentRequest so privilege groups can be tied to payments or subscriptions
        1. npm package for processing/submitting credit info?
        2. might restrict access to whole site (though more likely just parts) until payment made
      11. Need to remember to handle case of users added before privilege changes
      12. Update docs for any privilege additions/config
  8. Other pages
    1. Method to auto-create accessibility-friendly navigation bar, including login (root), logout, home, signup, and users (the special pages, 'activation', 'lostPassword', 'resetPassword', 'delete', 'reset', 'coverage', should not need to be added). Also add breadcrumbs and <link rel=next/prev>.

Lower priority to-dos

  1. Change POST APIs to GET where expected.
  2. Add to /accessAPI (GET) to explain API and also possibly consolidate to one page?
  3. "blockedIPs" pseudo-group to which one can add IPs (as distinct from "nogin.guests")
  4. Add types like number, string, and array (of strings) privileges?
  5. Add "local" boolean flag to privileges (if delivered by default in JavaScript)?
  6. Add /user/<username> (GET) script for admins and others
  7. Add /group/<groupname> (GET) script for admins and others
  8. Could make groups hierarchical (multiple? inheritance privileges)
  9. Allow variant of localScripts which uses CDN but generates fallback
  10. Option to email forgotten username (as a workaround, the reset password email will send this currently, but not if adding an option to disable the current uniqueEmails mode). Alternatively, could allow login by email. Don't want to show username for email in UI though for privacy reasons (more serious than just detecting that the user has an account, this would detect what their account was).
  11. We should already be checking the important items like avoiding existing names, but we should be rejecting bad values of lesser importance on the server-side as we do on the client-side (e.g., non-emails, too short of passwords, etc.)
  12. Review client-side validation for any other opportunities (e.g., for any missing required fields, etc.)
  13. Switch from jsdom to dominum (once latter may be capable), as latter is lighter-weight and we don't need all that jsdom offers; add tests within jamilih for the integration
  14. See about minor to-dos in code along the way
  15. Make email activation (and email) optional (would mitigate some problems with email detection when current enforcement of unique emails (proposed uniqueEmails: true) is enabled as users concerned with privacy could at least avoid an email that could be sniffed)?
  16. Make server config to make error messages of potential concern to privacy optional (avoid allowing testing presence of an email in the system by feedback from lost password (detecting existent vs. non-existent email)
    1. Only complete solution is if also allowing signup/update to an existing email (otherwise those pages could be used for detection instead).
      1. We could also make emails optional (beyond activation?)
      2. While we could make the errors less specific, this can still effectively be sniffed by signing up for different accounts and seeing if they result in some error (they likely shouldn't otherwise)
      3. In allowing disabling of uniqueEmails, require username be provided so will only send reset password for that account
    2. Info: Optional since many sites might wish to enforce unique emails or identity-by-email, or may simply wish to give users full feedback about whether a lost password email was successfully sent or not.
    3. Info: Note that login would always allow detecting existent vs. non-existent user names (this is just for email detection)
    4. More validation from CLI, e.g., adding an option or default to report if an email is already in use
5.1.0

4 months ago

6.1.0

4 months ago

6.3.0

4 months ago

6.5.0

3 months ago

7.3.0

3 months ago

7.1.0

3 months ago

7.6.1

3 months ago

7.6.0

3 months ago

7.2.4

3 months ago

7.2.3

3 months ago

6.0.6

4 months ago

6.0.1

4 months ago

6.0.0

4 months ago

6.0.3

4 months ago

6.2.0

4 months ago

6.0.2

4 months ago

6.0.5

4 months ago

6.4.0

4 months ago

6.0.4

4 months ago

7.0.0

3 months ago

7.4.0

3 months ago

7.2.2

3 months ago

7.2.1

3 months ago

7.2.0

3 months ago

7.0.2

3 months ago

7.0.1

3 months ago

7.7.0

2 months ago

7.5.0

3 months ago

5.0.0

5 months ago

4.0.0

2 years ago

3.0.0

2 years ago

2.6.1

3 years ago

2.6.0

3 years ago

2.5.2

3 years ago

2.5.4

3 years ago

2.5.3

3 years ago

2.5.0

3 years ago

2.5.1

3 years ago

2.3.0

3 years ago

2.4.0

3 years ago

2.2.0

3 years ago

2.1.0

4 years ago

2.0.0

4 years ago

1.4.0

4 years ago

1.3.0

4 years ago

1.2.0

4 years ago

1.0.0

5 years ago

1.0.0-rc.1

5 years ago

1.0.0-beta.5

5 years ago

1.0.0-beta.4

5 years ago

1.0.0-beta.3

5 years ago

1.0.0-beta.1

5 years ago