{"id":13624085,"url":"https://github.com/observing/tolkien","last_synced_at":"2025-04-05T11:30:32.221Z","repository":{"id":23860593,"uuid":"27238805","full_name":"observing/tolkien","owner":"observing","description":"Passwords are obsolete - Send one time tokens for authentication instead.","archived":false,"fork":false,"pushed_at":"2020-05-23T12:01:54.000Z","size":45,"stargazers_count":35,"open_issues_count":6,"forks_count":6,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-12-30T15:04:21.658Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/observing.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-11-27T19:30:39.000Z","updated_at":"2024-02-08T19:51:08.000Z","dependencies_parsed_at":"2022-07-10T10:19:02.552Z","dependency_job_id":null,"html_url":"https://github.com/observing/tolkien","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/observing%2Ftolkien","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/observing%2Ftolkien/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/observing%2Ftolkien/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/observing%2Ftolkien/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/observing","download_url":"https://codeload.github.com/observing/tolkien/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247330248,"owners_count":20921581,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T21:01:38.684Z","updated_at":"2025-04-05T11:30:31.919Z","avatar_url":"https://github.com/observing.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# tolkien\n\n[![Version npm][version]](http://browsenpm.org/package/tolkien)[![Build Status][build]](https://travis-ci.org/observing/tolkien)[![Dependencies][david]](https://david-dm.org/observing/tolkien)[![Coverage Status][cover]](https://coveralls.io/r/observing/tolkien?branch=master)\n\n[version]: http://img.shields.io/npm/v/tolkien.svg?style=flat-square\n[build]: http://img.shields.io/travis/observing/tolkien/master.svg?style=flat-square\n[david]: https://img.shields.io/david/observing/tolkien.svg?style=flat-square\n[cover]: http://img.shields.io/coveralls/observing/tolkien/master.svg?style=flat-square\n\n[Passwords are obsolete][obsolete]. If you haven't read this blog post yet, it\nshould be the first thing you do today. It's the concept which made this module\na reality.\n\n`tolkien implements one time token authorization which renders passwords\nobsolete. Instead of signing in to a service using a username and password you\nsign in using a token that get's send to you using (email, sms, whatever) and\nonce click the link/use the token you're authenticated. That's it.\n\n- You never have to store, crypte, hash and salt passwords again.\n- There is no need for forgot password and resets, everytime you need to\n  authenticate it sends you a new token.\n- Passwords can be changed without interaction of the service.\n- 1 input for registering and logging in. This lowers the barrier for sign-ups.\n- No passwords to remember, to generate and store. While solutions are 1Password\n  are great they do not solve the issue nor does everybody on the web use them.\n- SPAM/Scam free, ever got those phishing emails from sites asking for your user\n  name and passworld and secretly steal all your information? Yes, that's a\n  thing of the past.\n- It's super flexible, it's not just sending tokens through email but things\n  like SMS or even snail mail are possible.\n- Want two factor auth? Send tokens using multiple services (email and SMS).\n\n![YOU SHALL NOT PASS](http://media.giphy.com/media/njYrp176NQsHS/giphy.gif)\n\n**High five if you understood this reference**\n\n## Installation\n\nThis module is released in the public npm registry and can be installed using:\n\n```\nnpm install --save tolkien\n```\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Usage](#usage)\n  - [tolkien.service](#tolkienservice)\n  - [tolkien.login](#tolkienlogin)\n  - [tolkien.validate](#tolkienvalidate)\n  - [tolkien.get](#tolkienget)\n  - [tolkien.set](#tolkienset)\n  - [tolkien.remove](#tolkienremove)\n- [FAQ](#faq)\n- [License](#license)\n\n## Usage\n\nIn all examples we assume that you've already required the module as following:\n\n```js\n'use strict';\n\nvar Tolkien = require('tolkien');\n```\n\nNow that you've required the `Tolkien` module we need to initialize it. The\n`Tolkien` constructor allows one single argument which an options object which\nallows you to configure all the things. One of the most important things you\nneed to configure is the store where we can save the generated tokens etc. There\nare two ways to provide us with a store:\n\n1. Provide a store using the `store` property. (The store you supply should have\n   a `get(key, fn)`, `set(key, value, expire-ms, fn)` and `del(key, fn)` interface)\n2. Have the module configure a store for us. We use [Dynamis] as store wrapper.\n   This requires use of the `type` and `client` properties.\n\n```js\n//\n// Example of a dead simple custom memory store.\n// @see ./memory.js in the root of this repository.\n//\nvar data = Object.create(null);\n\nvar tolkien = new Tolkien({\n  store: require('./memory');\n});\n\n//\n// Or using dynamis interface.\n//\nvar redis = require('redis')\n  , client = redis.createClient();\n\nvar tolkien = new Tolkien({ type: 'redis', client: redis });\n```\n\nBut a store isn't the only thing you can configure. Here are all the different\noptions that we accept:\n\n- **`store`**, A store object which we can use to store the token.\n- **`type`**, The type of store we should generate using [Dynamis]. It can be\n  `redis`, `memcached` or `couchdb`.\n- **`client`**,  Reference to the client that [Dynamis] needs to wrap.\n- **`namespace`**, The prefix for all keys that we add in the store. Defaults to\n  `tolkien:`.\n- **`expire`**, Time that people have from generation of a token to using the\n  token. Can be human readable string which is parsed by the `ms` module or\n  a number which represents the amount of milliseconds it can take. Defaults to\n  `5 minutes`.\n\n### tolkien.service\n\nTolkien comes without any services configured by default as this service you\nwant to use usually specific to your application and requires further\nconfiguration. Luckily registering new services is dead simple. To add a new\nservice simply call the `service` method with the following arguments:\n\n1. `name`, A unique name of the service you're about to add. This name is later\n   used by you to tell us which service we should use to send the one time\n   authorization token.\n2. `callback`, This callback will be called every time someone needs to receive\n   a token. The callback will receive 2 arguments:\n   1. `data`, An object which contains the `token`, `id` and all other extra\n      properties you passed in to the [`tolkien.login`](login) method.\n   2. `next`, Completion callback for when you've send the token to the user.\n      This callback assumes an error first pattern.\n3. `options`, Optional object which allows you to further configure the service.\n   You can specify the following options:\n   - `type` What kind token do you want to receive. It can either be a **token**\n     which is cryptographically generated base58 string or **number** which is\n     cryptographically generated random number which is ideal for SMS/TXT\n   services. We default to `token` if no option is provided.\n   - `size` If you've selected **token** as type this is the amount of bytes we\n     need to generate for the token. And it will default to `16`. If you've\n     selected **number** as type it will be the maximum number that can be\n     generated. This defaults to `9999`.\n   - `expire`, Optional expire for the generated tokens. Some methods take more\n     time then others so you can use a custom timeout here (maybe you want to\n     send the generated token through snail mail ;-)). This defaults to the\n     value you've set in the constructor using the `expire` option.\n\n```js\n//\n// An example to setup email sending using the Mandrill e-mail service from\n// Mailchip.\n//\nvar mandrill = require('node-mandrill')(process.env.MANDRILL_API_KEY);\n\ntolkien.service('email', function email(data, fn) {\n  mandrill('/messages/send', {\n    message: {\n      to: [{ email: data.email, name: data.name }],\n      from_email: 'login@example.org',\n      subject: 'Hey, your example.org access-token!',\n      text: [\n        'ohai '+ data.name +'!',\n        'click the following link to login to your account:',\n        'http://example.com?token='+ data.token +'\u0026id'+ data.id,\n      ].join('\\n');\n    }\n  }, fn);\n}, { type: 'token' });\n```\n\nThe `data.token` is automatically generated by us. All the other properties are\npassed in to the [`tolkien.login(data, fn)`][login] method.\n\nThere is no limit to the amount of services you wish to configure nor is there a\nlimitation on the types of services you want to generate. You could send the\ngenerated token using:\n\n- Email\n- Text message\n- Automated phone calls\n- Social network direct messages (Twitter)\n- IRC, WhatsApp and other chat apps.\n\nThe possibilities are endless!\n\n### tolkien.login\n\nNow that you have the services configured you can start handling login attempts.\nThis is done by calling the login method. It requires 2 arguments:\n\n1. `data`, A object that contains a `service` property with the name of a\n   configured service and a `id` property which is the id of the user that wants\n   to authenticate. All other keys that you add will automatically be passed in\n   to service's callback function. Please note that this method automatically\n   adds a `token` property to the supplied object so any existing `token`\n   properties will be overridden.\n2. `fn`, A completion callback which follows the error first pattern. There a\n   couple of reasons on why a login can fail:\n   - You have no services configured.\n   - The id or service property is missing.\n   - You specified an unknown service name.\n   - The user still has a pending token that needs to expire.\n   - Token generation failed.\n   - Service failed.\n   - Storing failed.\n   - Retrieving from storage failed.\n\nIn the list of errors you might have noticed that an operation can fail if the\nuser already has a pending token. This might sounds odd but it's intentional.\nThe reason for this is to simply prevent multiple login attempts to happen and\nit protects you users against spam.\n\n```js\ntolkien.login({\n  service: 'email',\n  id: 'foobar'\n}, function (err, data) {\n  // Do stuffs\n});\n```\n\nIf you want to handle logins through express / http requests you could do\nsomething like:\n\n```js\napp.post('/login', function (req, res) {\n  if (!req.body.email) return res.end('missing email');\n\n  lookupOrRegister(req.body.email, function (err, id) {\n    if (err) return res.end('Failed things, '+ err.message);\n\n    tolkien.login({ \n      service: 'email',\n      id: id,\n      to: req.body.email\n    }, function (err) {\n      if (err) return res.end('Failed things, '+ err.message);\n\n      res.end('Check your email '+ req.body.email +' for your login token');\n    });\n  });\n});\n```\n\n## tolkien.validate\n\nAfter sending tokens we also need a way to validate them. This is done using the\n`validate` method. You should call this when you've received the generated\n`token` and `id` from your user. It requires 2 arguments:\n\n1. `data`, A data object which contains a `token` and `id` property which\n   contains the values that you received from the user.\n2. `fn`, A completion callback which follows the error first pattern. You can\n   receive an error when:\n   - The token or id is missing from the data object.\n   - We failed to retrieve the data from the storage layer.\n   - We failed to remove the token from the storage layer.\n   Please do note that an error does not always indicate that a user has also\n   failed to validate as we only remove token after we've validated the incoming\n   data so you should always check the `validation` argument of this callback.\n\n```js\ntolkien.validate({ token: 'foo', id: 'bar' }, function (err, validates, data) {\n  // validates: Boolean indicating if the credentials are correct.\n  // data: Reference to the object you passed in the validate function.\n  // err: Thinks broke.\n});\n```\n\nIf you want to handle logins through express / http requests you could do\nsomething like:\n\n```js\napp.get('/auth', function (req, res) {\n  if (!req.query.id || !req.query.token) return res.end('Missing required params');\n \n  tolkien.validate(req.query, function (err, validates, data) {\n    if (!validates) res.redirect('/login'); // Login again\n\n    req.session.id = data.id;\n    res.end('Successfully logged in');\n  });\n});\n```\n\n### tolkien.get\n\n**Please note this is a private API - but might still be useful for you.**\n\nHelper function which will retrieve the data from the storage layer. If the\nsupplied object only has a token it will search the id, if it has an id it will\nsearch the token.\n\n```js\ntolkien.get({ id: 'foo' }, function (err, data) {\n  console.log(data.id);    // foo\n  console.log(data.token); // bar\n});\n```\n\n### tolkien.set\n\n**Please note this is a private API - but might still be useful for you.**\n\nHelper function which will add the data to the storage layer.\n\n```js\ntolkien.set({ id: 'foo', token: 'bar' }, expiree, function (err, data) {\n  console.log(data.id);    // foo\n  console.log(data.token); // bar\n});\n```\n\n### tolkien.remove\n\n**Please note this is a private API - but might still be useful for you.**\n\nRemove the token and id from the data layer.\n\n```js\ntolkien.remove({ id: 'foo', token: 'bar' }, function (err, data) {\n  console.log(data.id);    // foo\n  console.log(data.token); // bar\n});\n```\n\n## FAQ\n\nFrequently Asked Questions:\n\n### How do I handle registration or send a different e-mail for registration?\n\nTolkien sees no difference between registration and login as both actions\nrequire the interactions: Supplying us with a user id and then send the token.\nBut this does not mean that **you** as developer cannot tell the difference. We\ndirectly pass the provided object from the [`tolkin.login`][login] in to your\nconfigured service so you can add extra property like `registration: true` to\nthe data object and send a different message.\n\n```js\ntolkien.service('email', function email(data, fn) {\n  var template;\n\n  //\n  // FYI: Don't fs.readFileSync in production, this is merely for illustrative\n  // purposes.\n  //\n  if (data.registration) {\n    template = fs.readFileSync(__dirname +'/registration.txt', 'utf-8');\n  } else {\n    template = fs.readFileSync(__dirname +'/login.txt', 'utf-8');\n  }\n\n  // use the Templatetron3000 to compile our template..\n  template = template.replace('{token}', data.token)\n                     .replace('{id}', data.id);\n\n  youremailmodulefunction(template, data.to, fn);\n});\n\ntolkien.login({ \n  service: 'email',           // Use the `email` service.\n  registration: true,         // Checked in the service.\n  id: 'sp3c14lus3r1d',        // Required.\n  to: 'hooman@example.com'    // Address we want to email to, used the service.\n}, function send(err) {\n  if (err) return retrylogin(err);\n\n  console.log('yay, token send');\n});\n```\n\n### I want to use a custom token generator, how is this possible.\n\nThis is definitely possible. We have a special `.extend` property on our\nconstructor which allows you create your own custom Tolkien instances and your\nown custom token generation methods. If you want to create custom token type\ngenerator you can do:\n\n```js\nvar Gandalf = Tolkien.extend({\n  // size is the amount of bytes the service needs for the token.\n  token: function generator(size, fn) {\n    hardcorecryptoactiontron3000(size, function (err, data) {\n      fn(err, data.toString('hex'));\n    });\n  }\n});\n\nvar tolkien = new Gandalf({ store: require('./memory') });\n```\n\nAnd to custom number generator you can do:\n\n```js\nvar Gandalf = Tolkien.extend({\n  // size is the maximum number set the service.\n  number: function generator(size, fn) {\n    customcryptonumbergeneratorcurveninja(size, fn);\n  }\n});\n\nvar tolkien = new Gandalf({ store: require('./memory') });\n```\n\n## License\n\nMIT\n\n[Dynamis]: https://github.com/Moveo/dynamis\n[login]: #tolkienlogin\n[obsolete]: https://medium.com/@ninjudd/passwords-are-obsolete-9ed56d483eb\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fobserving%2Ftolkien","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fobserving%2Ftolkien","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fobserving%2Ftolkien/lists"}