accounts
accounts copied to clipboard
AccountsJS Improvements
As you can see, I have been doing some work on the repo
- [x] Add
@accounts/rest-expressand@accounts/rest-clientto the mono repo - [x] Add
@accounts/mongoto the mono repo - [x] Add
@accounts/error- #171 - [x] Add
@accounts/types- #173 - [x] Make the accounts packages share the same pattern - #186
- [x] Improve Interfaces names consistency / agree on a naming convention
- [x] Add
@accounts/database-manager- #174 - [x] Add
@accounts/token-manager- PR open : #222 - [x] Improve Transport - Auth relationship
- [x] Implement Token-Transport
- [x] Create a separate modular notification system ( email, phone, etc... )
- [ ] Return errors instead of throw them
The instanciation i would like to achieve is this one (collapse)
/*
>=> ========================= >=>
ACCOUNTS-JS SETUP EXAMPLE
>=> ========================= >=>
*/
// ============================= ==>
// >=> import Accounts-Js packages
// ============================= ==>
// =================================================
// Accounts Server => Core
// Permit to tie all packages together
import AccountsServer from '@accounts/server';
// =================================================
// Accounts Mongo => Database Interface
// Provides Access to a database
import MongoInterface from '@accounts/mongo';
//==================================================
// Transport Express => Network Interface
// Provides the Express middleware connecting your express application to Accounts-JS
import TransportExpress from '@accounts/express';
//==================================================
// Token Transport Express Headers => Token Storage
// Defines the way to store the tokens while sending them to the client
import TokenTransportExpressHeaders from '@accounts/express-token-headers'; // Headers => Store in request headers
import TokenTransportExpressCookies from '@accounts/express-token-cookies'; // Cookies => Store in request cookies
import TokenTransportExpressBody from '@accounts/express-token-body'; // Body => Store in request body
//==================================================
// Token Transport Manager => Token Storage
// Allow multiple TokenTransport
import TokenTransportManager from '@accounts/token-transport-manager';
// =================================================
// Password Service => Authentication By Password
import PasswordService from '@accounts/password';
// =================================================
// Email Service => Email Sender Service
// Provide an Interface to send emails
import EmailService from '@accounts/email-debug'; // Email Debug => console.log
//import EmailService from '@accounts/email-mailgun'; // Email Mailgun => mailgun sender
// =================================================
// Email Plugin Password => Email Notification Service Plugin for Password Authentication
// Provide a set of email templates to an Email Service
import EmailPluginPassword from '@accounts/email-plugin-password';
// =================================================
// TokenManager => JWT Management
// Generate and decode accessTokens and refreshTokens
import TokenManager from '@accounts/token';
// ================= ==>
// >=> instanciation
// ================= ==>
// >=> Database Interface
// Get a Mongo Database Object or a Promise returning one
import Connection from './connection';
// Create the Database Interface
const databaseInterface = new MongoInterface(Connection);
// >=> Transport
// Select a way to store tokens
const tokenTransportHeaders = new TokenTransportExpressHeaders()
const tokenTransportCookies = new TokenTransportExpressCookies({
access:{
secure: false,
httpOnly: false,
domain: 'localhost',
sameSite: false,
},
refresh:{
secure: false,
httpOnly: false,
domain: 'localhost',
sameSite: false,
}
})
const tokenTransportBody = new TokenTransportExpressBody()
// If multiple token transport selected, the Token Transport Manager must be used
const tokenTransport = new TokenTransportManager(tokenTransportHeaders, tokenTransportCookies, tokenTransportBody)
// Build the transport
const transport = new TransportExpress({
tokenTransport,
})
// Extract the authentication middleware and export it to consume it on express
export const authMiddleware = transport.middleware;
//extract the accounts router and export it to consume it on express
export const accountsRouter = transport.router;
// >=> Authentication
// instanciate your authentication services
const passwordService = new PasswordService();
// >=> Notification
// instanciate the notification plugins needed to provide notification support to authentication services
const emailPluginPassword = new EmailPluginPassword();
// instanciate your notifications services
const emailService = new EmailService({
notificationPlugins: [ emailPluginPassword ]
})
// >=> Token Manager
// instanciate the Token Manager
const tokenManager = new TokenManager({
secret: 'e'
});
// ================= ==>
// >=> Accounts Server
// ================= ==>
const accountsServer = new AccountsServer({
databaseInterface,
transport,
tokenManager,
authenticationServices: [ passwordService ],
notificationServices: [ emailService ]
})
Flow Ideas
The actions made by accountsJS can be split into 3 categories
-
The ANONGUY want to Authenticate => get tokens
- via password
- via code sent on phone
- via gpg
- via oauth
- via twitter
- via google
- via MFA
- password and code
- password and code and gpg
-
The KNOWNGUY want to use the authorization of his tokens
- Impersonate
- get session
- logout
- reset Password
- send Enroll Email
- send Verify Password Email
- unlink OAuth
-
The ANONGUY want to execute actions which doesn't need rights
- send Reset Password Email
- register
So in my opinion we need to change the way the suite works.
The current way of doing things limit the "multistep" behavior of modern authentication methods
In the case of just using the authorization, we just have to get the DBUser from the DB via tokens, and then apply dev defined "verifyFunctions" as we does for impersonating
In the case of authorization, we should first identify the ANONGUY
// it could be :
const input = {
identity: {
email: '[email protected]',
}
}
// or :
const input = {
identity: {
username: 'aaaaaa',
}
}
// or even :
const input = {
identity: {
userId: 25,
username: 'aaaaaa'
}
}
Well, any information able to identify the ANONGUY
Then we use thoses informations to retrieve the DBUser from database
Now, in order to identify the ANONGUY, we should read on the DBUser the authentication requirements
const DBUser = {
requirements: {
AND: ['password', 'code']
}
}
const DBUser = {
requirements: [
{ OR: ['password', 'facebook', 'twitter'] },
'code'
]
}
Then, we should provide a way to incrementally satisfy the requirements, and I propose to do this by using the session.
See the auth chart for details on the flow : https://drive.google.com/file/d/1k9SAoqx7RLiF-1RNH0beOmtkUPQCeztc/view?usp=sharing
Also, An authentication Service should provide a way to identify an user
For now, each time we want to add such a way, we need to add a corresponding database function. In the future maybe it will be appropriate to provide a plugin API in our DatabaseInterfaces, but It seems inappropriate now.
So let's say we want to add a service able to identify the user with 'shortId'
We would have to add the findUserByShortId method to the databaseInterfaces, of course, but in order for the identification method on the AccountsServer to be able to recognize an user by it's shortId, we would just have to add a public object to the class:
const input = {
identity: {
shortId: 14
}
}
class ShortIdService implements AuthenticationService {
public identificationMethods = { shortId: 'findUserByShortId' }
}
Then, the accountsServer identify method should interpret the presence of shortId as a need to use the findUserByShortId function on the databaseInterface.
With this method, we allow the use of any combination of authenticationServices but we also simplify the development of the authenticationServices themselves
@Aetherall
- Totally agree about the need of multi steps for authentication.
- The identification method design looks good to me.