0.0.27 • Published 3 years ago

puremvc-express v0.0.27

Weekly downloads
74
License
ISC
Repository
-
Last release
3 years ago

Intruduction

Puremvc-Express module is a bootstrap kit to help you to build backend service. Especially for developers who are familiar with Puremvc and Express.

Usage

Initial

/** 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;

Dependency Injection

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');
    }

Logger

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
    }
}

Register your customized Proxy, Mediator, Command

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();

Serving static files

Puremvc-express defaults serve 'public' folder

Local stretagy of Passport

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);

Authenticate required path

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>' ...

Simple Users

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" }
    ]

Included API's

Sign in

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

Sign up

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

Sign JWT

  • 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

Echo message

  • Path: /echo/:message
  • Method: ALL
  • Return: message
  • CURL:
    curl localhost:3000/echo/hello%20world

Authorized echo message

  • Path: /api/echo/:message
  • Method: ALL
  • Header: authorization: bearer <token>
  • Return: message
  • CURL:
    curl -H "authorization: bearer <token>" localhost:3000/api/echo/hello%20world

2 Factor Authorization

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.

Options
    {
        auth: {
            twofa: {
                qrCodeFormat: '[png | svg] default: png'
            }
        }
    }
Router middleware
    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>
  • 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

Significant events

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.

Log levels

Winston's default log levels.

  • error
  • warn
  • info
  • http
  • verbose
  • debug
  • silly

Environment preparation

Database

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

Sign your own SSL certificate

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