Puremvc-Express module is a bootstrap kit to help you to build backend service. Especially for developers who are familiar with Puremvc and Express.
/** index.js */
const fs = require('fs');
const winston = require('winston');
let { CORE_EVENTS, App } = require('puremvc-express');
let options = require('./options');
let app = new App('test', options);
app.start();
/** options.js */
const winston = require('winston');
const { format } = require('winston');
const fs = require('fs');
const options = {
httpServer: {
port: 3000,
sslPort: 3002,
credentials: {
key: fs.readFileSync('./ssl/key.pem', 'utf8'),
cert: fs.readFileSync('./ssl/cert.pem', 'utf8')
},
cors: {
credentials: true,
origin: [
'https://localhost:8080'
]
}
},
database: {
host: 'database.your.domain',
user: 'dbuser',
password: 'dbpassword',
database: 'dbname',
connectionLimit: 2
},
memCache: {
host: 'redis.your.domain',
port: 6379
},
firebase: {
apiKey: 'yourFirebaseApiKey',
authDomain: 'yourAuthDomain.firebaseapp.com',
databaseURL: 'https://yourFirebaseProjectId.firebaseio.com',
projectId: 'yourFirebaseProjectId',
storageBucket: 'yourFirebaseProjectId.appspot.com',
messagingSenderId: '1234567890',
appId: '1:1234567890:web:0123456789abcdef',
measurementId: 'G-0123456789'
},
passport: {
jwt: {
secret: 'yourJwtSecret',
signOptions: {
algorithm: 'algorithm-you-want',
expiresIn: '900s'
}
},
local: {
usernameField: 'username',
passwordField: 'password'
},
facebook: {
clientID: 'facebookID',
secret: 'facebookSecret',
callbackURL: 'https://localhost:3000/auth/facebook/callback'
},
twitter: {
consumerKey: 'twitterKey',
consumerSecret: '',
callbackURL: ''
},
google: {
consumerKey: 'googleKey',
consumerSecret: '',
callbackURL: ''
}
},
session: {
secret: 'some secret',
resave: false,
saveUninitialized: false
},
di: {
m_appData: 'CORE:APP-DATA',
m_httpServer: 'CORE:HTTP-SERVER',
m_database: 'CORE:DATABASE',
m_passport: 'CORE:PASSPORT',
m_memCache: 'CORE:MEM-CACHE',
m_logger: 'CORE:LOGGER',
m_users: 'CORE:USERS'
},
logger: {
level: 'info',
transports: [
new winston.transports.Console({
format: format.combine(
format.colorize(),
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
format.printf(info => `${info.timestamp} ${info.level} [${info.class}]: ${info.message}`)
)
}),
new winston.transports.File({
format: format.combine(
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
format.prettyPrint(),
),
filename: 'logs/app.log'
})
]
},
users: [
{ id: 1, username: 'root', password: 'p4s5w0rD', role: 'admin' },
{ id: 2, username: 'trumpjojo', password: '9453', role: 'author' },
{ id: 3, username: 'ingrid', password: '0908', role: 'people\'s gf' },
{ id: 4, username: 'dora', password: '9ido', role: 'amon' }
]
}
export default options;
This feature supports inject object which extends {puremvc.Proxy} For example
import { Mediator } from 'puremvc';
import { CORE_EVENTS } from 'puremvc-express';
export default class MyMediator extends Mediator {
m_logger = undefined;
logger;
constructor () {
super('NAME')
}
onRegister () {
this.sendNotification(CORE_EVENTS.DI, this);
// Now you can access this.m_logger
}
}
Object injected depends on values under di
of options, corresponding to the string assigned to super method. This feature use retrieveProxy in puremvc class to approach.
consturctor () {
super('NAME USE TO INJECT');
}
Puremvc-Express uses Winston as logging tool, we wrap it to similar as Log4js. By following example, you can learn how to use it.
class YourClass extends SimpleCommand {
m_logger = undefined;
logger;
constructor () {
super('NAME');
}
execute (notification) {
this.sendNotification(CORE_EVENTS.DI, this);
this.logger = this.m_logger.getLogger(this.constructor.name);
this.logger.info('Hello World');
//2021-03-02 13:44:24.643 info [YourClass]: Hello World
}
}
After puremvc-express was constructed, you can access its facade member then register following three commnand to prepare your Proxy
, Mediator
and Command
.
let { CORE_EVENTS, App } = require('puremvc-express');
let app = new App('AppName');
let facade = app.facade;
facade.registerCommand(CORE_EVENTS.INIT_COMMANDS, YourCommandRegistor);
facade.registerCommand(CORE_EVENTS.INIT_MEDIATORS, YourMediatorRegistor);
facade.registerCommand(CORE_EVENTS.INIT_PROXIES, YourProxyRegistor);
app.options = {...options};
app.start();
Puremvc-express defaults serve 'public' folder
In order to maping to you user data verifing, please register a command whic listening to CORE_EVENTS.USER_VERIFY
.
class UserVerify extends SimpleCommand {
m_database = undefined;
m_logger = undefined;
logger;
constructor() {
super()
};
async execute(notification) {
this.sendNotification(CORE_EVENTS.DI, this);
this.logger = this.m_logger.getLogger('UserVerify');
let { username, password, done } = notification.body;
let result = await this.m_database.query(
`SELECT * from users where username like "${username}";`
);
let hit = await result.filter((obj, idx, ary) => {
return obj.loginID == username;
})
if (hit.length && await bcrypt.compare(password, hit[0].password)) {
done(null, hit[0]);
} else
done(null, false);
}
}
facade.registerCommand(CORE_EVENTS.USER_VERIFY, UserVerify);
Puremvc-Express default provides bearer token checking mechanism for every request paths under http://localhost:3000/auth/...
, the request header will need bearer token curl -H 'authorization: bearer <token>' ...
This kit provides you a simple way to setup your user list, assign a users
key in option json, and assign an array to it.
users: [
{ "id": 1, "username": "root", "password": "p4s5w0rD", "role": "admin" },
{ "id": 2, "username": "user001", "password": "letmein", "role": "user" },
{ "id": 3, "username": "audit001", "password": "iamwaching", "role": "audit" },
{ "id": 4, "username": "guest001", "password": "takealook", "role": "guest" }
]
This API sends notifcation CORE_EVENTS.AUTH_SIGNIN
with parameter req
, res
, next
.
If there has the event name registered.
- Path:
/auth/signin
- Method:
POST
This API sends notification CORE_EVENTS.AUTH_SIGNUP
with parameter req
, res
, next
.
If there has the event name registered.
- Path:
/auth/signup
- Method:
POST
- Path:
/auth/signjwt
- Method:
POST
- Header:
content-type: application/json
- Body:
{ username: 'user', password: 'pwd', payload: { your:'data' } }
- Return: Bearer token using payload object.
- CURL:
curl -H "content-type: application/json" -d '{"username":"root","password":"p4s5w0rD","payload":{"your":"data"}}' localhost:3000/auth/signjwt
- Path:
/echo/:message
- Method:
ALL
- Return: message
- CURL:
curl localhost:3000/echo/hello%20world
- Path:
/api/echo/:message
- Method:
ALL
- Header:
authorization: bearer <token>
- Return: message
- CURL:
curl -H "authorization: bearer <token>" localhost:3000/api/echo/hello%20world
Puremve-express's 2FA scheme needs two commands registered via puremvc's registerCommand.
Which events was CORE_EVENTS.AUTH_REGISTER_TOTP_SECRET
and CORE_EVENTS.AUTH_RETRIEVE_TOTP_SECRET
, you have to use these two events to manipulate your totp secret of user.
{
auth: {
twofa: {
qrCodeFormat: '[png | svg] default: png'
}
}
}
routerAuth.get('/path/to', passport.authenticate('2fa-totp'), (req,res,next) => yourMethod);)
class RegisterTotpSec extends SimpleCommand {
//...
execute(notifcation) {
let {res, username, totpSecret} = notifcation.body;
let user = Users.findOne(x => x.username == username);
user.totpSecret = totpSecret;
// Your logic here to decide what http code should be return.
res.status(200).end()
}
}
class RetrieveTotpSec extends SimpleCommand {
//...
execute(notification) {
let {user, done} = notification.body;
let dbUser = Users.findOne(x => x.username == user.username);
user.totpSecret = dbUser.totpSecret;
done(user);
}
}
And a resource under /api/auth/2fa
, it's means request to this needs with bearer token.
Retrieve QR code.
- Path:
/api/auth/2fa
- Method:
GET
- Header:
authorization: bearer <token>
- Return:
png image
- Header:
set-cookie: <cookie>
- Header:
- CURL:
curl -H "authorizatoin: bearer <token>" localhost:3000/api/auth/2fa
Confirm and register totp secret.
- Path:
/api/auth/2fa
- Method:
POST
- Header:
authorization: bearer <token>, cookie: <cookie>
- Return:
200
- CURL:
curl -H "authorization: bearer <token>" -H "cookie: <cookie>" -XPOST localhost:3000/api/auth/2fa
Verify totp
- Path:
/api/echo/2fa/:message
- Method:
GET
- Header:
authorization: bearer <token>
- Return:
message
- CURL:
curl -H "authorization: bearer <token>" -d "code=<code>" localhost:3000/api/echo/2fa/message
Puremvc-express fires following events after some specific process done. Before using these events, simply import CORE_EVENTS e.g. let { CORE_EVENTS } from 'puremvc-express';
and listen to it.
- DATABASE_CONNECTED
Database connected. - INIT_COMMANDS
Time to register your own commands. - INIT_DATABASE_TABLE
When no tables exists in database. - INIT_MEDIATORS
Time to register your own mediators. - INIT_PROXIES
Time to register your own proxies. - PRE_INIT_PROXIES
Useless event unless you want override logger what we provided. - SERVER_STARTED
HTTP server started, it's time to register your http resource. - USER_VERIFY
Verfiy user via passport's local strategy, listen this event and query your own user database.
Winston's default log levels.
- error
- warn
- info
- http
- verbose
- debug
- silly
docker run -d --name db \
-p 3306:3306 \
-p 33060:33060 \
-e MYSQL_ROOT_PASSWORD=rootpwd \
-e MYSQL_DATABASE=mydb \
-e MYSQL_USER=user \
-e MYSQL_PASSWORD=userpwd \
mariadb \
--character-set-server=utf8mb4 \
--collation-server=utf8mb4_unicode_ci \
--default-authentication-plugin=mysql_native_password
refer: https://github.com/FiloSottile/mkcert
docker run \
-d \
--rm \
-e "domain=*.yourdo.main,yourdo.main,127.0.0.1,localhost" \
--name mkcert \
-v $(pwd)/ssl/:/root/.local/share/mkcert \
vishnunair/docker-mkcert