<repo_name>awi2017-option1group1/Prello-auth<file_sep>/src/util/login.ts
import * as express from 'express'

import { config } from '../config'

import { UserFacade } from '../bl/userFacade'

export const login = async (userId: number, res: express.Response) => {
    const token = await UserFacade.authenticate(userId)
    res.cookie(config.loginCookieName, token, { 
        httpOnly: true, 
        sameSite: true, 
        secure: config.env !== 'development' 
    })
}
<file_sep>/src/util/oauthMiddlewares.ts
import * as Oauth2Server from 'oauth2-server'

import { AccessTokenFacade } from '../bl/accessTokenFacade'
import { AuthorizationCodeFacade } from '../bl/authorizationCodeFacade'
import { ClientFacade } from '../bl/clientFacade'

const model = {
    getAuthorizationCode: AuthorizationCodeFacade.getById,
    saveAuthorizationCode: AuthorizationCodeFacade.getOrCreate,
    revokeAuthorizationCode: AuthorizationCodeFacade.delete,

    getAccessToken: AccessTokenFacade.getById,
    saveToken: AccessTokenFacade.create,

    getClient: ClientFacade.getByIdAndSecret,

    verifyScope: () => Promise.resolve(true)
}

const oauth = new Oauth2Server({ model })
const Request = Oauth2Server.Request
const Response = Oauth2Server.Response

export const tokenMiddleware = (req, res, next) => {
    const request = new Request(req)
    const response = new Response(res)

    oauth.token(request, response)
    .then((token) => {
        res.set(response.headers).json(response.body)
    })
    .catch(err => {
        console.log(err)
        res.status(err.status).json({ message: err.message })
    })
}

export const authorizeMiddleware = (req, res, next) => {
    const request = new Request(req)
    const response = new Response(res)

    const options = {
        authenticateHandler: {
            handle: (data) => {
                return req.user
            }
        },
        allowEmptyState: true
    }

    if (req.body.cancel) {
        return res.redirect(req.query.redirect_uri)
    }

    oauth.authorize(request, response, options).then((authorizationCode) => {
        res.status(response.status).set(response.headers).end()
    })
    .catch(err => {
        res.status(err.status).json({ message: err.message })
    })
}
  
export const authenticateMiddleware = (req, res, next) => {
    const request = new Request(req)
    const response = new Response(res)
  
    oauth.authenticate(request, response)
    .then((token) => {
        Object.assign(req, { token })
        next()
    })
    .catch(err => { 
        res.status(err.status).json({ message: err.message })
    })
}
<file_sep>/README.md
# Prello-auth
Prello's authentification application.

[![CircleCI](https://circleci.com/gh/awi2017-option1group1/Prello-auth/tree/master.svg?style=svg)](https://circleci.com/gh/awi2017-option1group1/Prello-auth/tree/master)

- - - - - - - - -

## Installation

Before installing Prello-auth, you need to have Prello-back installed.

- Clone the github repository. 

    `git clone https://github.com/awi2017-option1group1/Prello-auth`
- Install the dependencies for development mode 

	`npm install`  

## Execution

- Complete and update the `.env.dev` file then source it

    `source .env`
- To run the application in development mode 

    `npm run start:dev`
- To run the application in production mode

	`npm run build && npm start`
- To run the tests

	`npm test`

- - - - - - - - -

## Dependencies

- Prello back end application is written in `Typescript`.  
- It is built with `Express`.
- We use `TypeORM` as ORM for the application.  
- To test the application we use `Jest`.

- - - - - - - - -

## Contributing

Please follow the Google Angular guidelines: 
[Guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#-git-commit-guidelines)

## Useful links to look at before contributing
- [TypeORM documentation](http://typeorm.io/#/)

<file_sep>/copyStaticAssets.js
var shell = require('shelljs');

shell.cp('-R', 'src/views/', 'dist/views/');
<file_sep>/src/routes/user.ts
import * as express from 'express'

import { AccessTokenFacade } from '../bl/accessTokenFacade'
import { UserFacade } from '../bl/userFacade'

export class UserController {

    static async getTokens(req: express.Request, res: express.Response) {
        const tokens = await AccessTokenFacade.getAllByUser(req.token.user.id)
        return res.json({ tokens })
    }

    static async getTokenData(req: express.Request, res: express.Response) {
        switch (req.params.type) {
            case 'header':
                const accessToken = await AccessTokenFacade.getById(req.params.token)
                if (accessToken) {
                    res.status(200).send({ user: { uid: accessToken.user.uid } })
                }
                res.status(404).json({ error: 'Invalid token' })
                break

            case 'cookie':
                const user = await UserFacade.getByToken(req.params.token)
                if (user) {
                    res.status(200).send({ user: { uid: user.uid } })
                }
                res.status(404).json({ error: 'Invalid token' })
                break

            default:
                res.status(400).json({ error: 'Invalid token type' })
                break
        }
    }

    static async getMe(req: express.Request, res: express.Response) {
        return res.json({ 
            me: {
                username: req.user.username,
                email: req.user.email,
                uid: req.user.uid,
                avatarColor: req.user.avatarColor
            } 
        })
    }

    static async getMyUser(req: express.Request, res: express.Response) {
        req.user = req.token.user
        return UserController.getMe(req, res)
    }

}
<file_sep>/src/util/passport.ts
import * as passport from 'passport'

import { config } from '../config'
import { fullUrlFromString, AUTH_HOST } from './url'

import { UserFacade } from '../bl/userFacade'

const CookieStrategy = require('passport-cookie').Strategy
passport.use(new CookieStrategy({ cookieName: config.loginCookieName }, (token, done) => {
    UserFacade.getByToken(token).then(
        user => done(null, user),
        error => done(error)
    )
}))

export default passport

interface EnsureLoginOptions {
    redirect?: boolean
}

const redirectToLogin = (req, res) => {
    return res
        .cookie(config.redirectCookieName, `/${config.server.authSuffix}${req.url}`, {
            httpOnly: true, 
            sameSite: true, 
            secure: config.env !== 'development',
            maxAge: 300000
        })
        .redirect(fullUrlFromString(`/login`, AUTH_HOST))
}

export const ensureLogin = (options?: EnsureLoginOptions) => (req, res, next) => {
    passport.authenticate('cookie', { session: false }, (authErr, user, infos) => {
        if (authErr || !user) {
            if (options && options.redirect) {
                redirectToLogin(req, res)
            } else {
                res.status(401).json({ error: 'Unauthorized' })
            }
        } else {
            req.user = user
            next()
        }
    })(req, res, next)
}
<file_sep>/src/routes/oauth.ts
import * as express from 'express'

import { fullUrlFromReq, AUTH_HOST } from '../util/url'

import { tokenMiddleware, authorizeMiddleware } from '../util/oauthMiddlewares'

import { AccessTokenFacade } from '../bl/accessTokenFacade'
import { ClientFacade } from '../bl/clientFacade'

export class OauthController {

    static async getAuthorize(req: express.Request, res: express.Response, next: express.NextFunction) {
        const client = await ClientFacade.getById(req.query.client_id)
    
        // Bypass the user confirmation for trusted apps
        if (client) {
            if (client.isTrusted) {
                return authorizeMiddleware(req, res, next)
            } else {
                const token = await AccessTokenFacade.getByClientAndUser(client.id, req.user.id)
                if (token) {
                    return authorizeMiddleware(req, res, next)
                }
            }
        }
    
        return res.render('authorize', {
            user: req.user,
            client,
            redirect_uri: fullUrlFromReq(req, AUTH_HOST)
        })
    }

    static postAuthorize(req: express.Request, res: express.Response, next: express.NextFunction) {
        return authorizeMiddleware(req, res, next)
    }

    static postToken(req: express.Request, res: express.Response, next: express.NextFunction) {
        return tokenMiddleware(req, res, next)
    }

    static getDone(req: express.Request, res: express.Response) {
        return res.render('done', { // views: done
            type: 'authorization_code',
            code: req.query.code,
            state: req.query.state,
            to: req.query.to || '*'
        })
    }

}
<file_sep>/Dockerfile
FROM node:8.7.0

# Create app directory
RUN mkdir -p /src/app
WORKDIR /src/app

# Install app dependencies
COPY package.json /src/app/
RUN npm install

# Bundle app source
COPY . /src/app

# Build and optimize react app
RUN npm run build

# Set the command to start the node server.
CMD npm start
<file_sep>/src/util/oauth.ts
import { config } from '../config'

const Oauth2Service = require('oauth').OAuth2

export const GithubService = new Oauth2Service(
    config.github.clientID, 
    config.github.clientSecret, 
    'https://github.com/', 
    'login/oauth/authorize', 
    'login/oauth/access_token'
)
<file_sep>/src/server.ts
import { createConnection } from 'typeorm'
import  { fromConfig } from './database'

import { app } from './app'

createConnection(fromConfig())
.then(async connection => {
    app.listen(app.get('port'), () => {
        console.log(('App is running at http://localhost:%d in %s mode'), app.get('port'), app.get('env'))
        console.log('Press CTRL-C to stop\n')
    })
}).catch(error => console.log(error))
<file_sep>/src/bl/authorizationCodeFacade.ts
import { getRepository } from 'typeorm'

import { AuthorizationCode } from 'oauth2-server'

import { OAuth2AuthorizationCode } from '../entities/AuthorizationCode'
import { OAuth2Client } from '../entities/Client'
import { OAuth2User } from '../entities/User'

export class AuthorizationCodeFacade {

    static async getById(id: string) {
        return await getRepository(OAuth2AuthorizationCode).findOne({
            relations: ['client', 'user'],
            where: {
                authorizationCode: id 
            } 
        }).then(code => {
            if (code) {
                const localCode = code!
                localCode.expiresAt = new Date(localCode.expiresAt)
                return localCode
            }
            return code!
        })
    }

    static async getOrCreate(code: AuthorizationCode, client: OAuth2Client, user: OAuth2User) {
        const localCode = await AuthorizationCodeFacade.getByClientAndUser(client.id, user.id)
        if (localCode) {
            return localCode
        } else {
            code.client = client
            code.user = user
            code.expiresAt = new Date(2048, 1)
            return await getRepository(OAuth2AuthorizationCode).save(code)   
        }
    }

    static async getByClientAndUser(clientId: string, userId: string) {
        return await getRepository(OAuth2AuthorizationCode)
            .createQueryBuilder('code')
            .leftJoin('code.user', 'user')
            .leftJoin('code.client', 'client')
            .where('user.id = :userId', { userId })
            .where('client.id = :clientId', { clientId })
            .getOne()
            .then(code => code!)
    }

    static async delete(code: AuthorizationCode) {
        await getRepository(OAuth2AuthorizationCode).deleteById(code.authorizationCode)
        return true
    }

}
<file_sep>/.circleci/setup-heroku.sh
#!/bin/bash
git remote add heroku https://git.heroku.com/prello-auth.git
git config --global user.email "<EMAIL>"
git config --global user.name "<NAME>"

wget https://cli-assets.heroku.com/branches/stable/heroku-linux-amd64.tar.gz
sudo mkdir -p /usr/local/lib /usr/local/bin
sudo tar -xvzf heroku-linux-amd64.tar.gz -C /usr/local/lib
sudo ln -s /usr/local/lib/heroku/bin/heroku /usr/local/bin/heroku

cat > ~/.netrc << STOP
machine api.heroku.com
  login $HEROKU_LOGIN
  password $HEROKU_API_KEY
machine git.heroku.com
  login $HEROKU_LOGIN
  password $HEROKU_API_KEY
STOP

# Add heroku.com to the list of known hosts
mkdir -p ~/.ssh
ssh-keyscan -H heroku.com >> ~/.ssh/known_hosts
<file_sep>/src/database.ts
import { ConnectionOptions } from 'typeorm'

import { config } from './config'

export const fromConfig = (): ConnectionOptions => ({
    ...config.database,
    synchronize: false,
    entities: [
        `${__dirname}/entities/*.js`
    ],
    migrations: [
        `${__dirname}/migrations/*.js`
    ],
    logging: ['query', 'error']
})
<file_sep>/src/util/internalMiddleware.ts
import { config } from '../config'

export const ensureInternal = () => (req, res, next) => {
    const authHeader = req.get('authorization')
    if (authHeader) {
        if (authHeader.substring('Bearer '.length) === config.internalToken) {
            return next()
        }
    }
    res.status(403).json({ error: 'Forbidden' })
}
<file_sep>/src/entities/User.ts
import { Entity, Column, Generated, PrimaryGeneratedColumn } from 'typeorm'

@Entity('user')
export class OAuth2User {

    @PrimaryGeneratedColumn({
        name: 'id'
    })
    uid: number

    @Column({
        name: 'uuid',
        type: 'varchar',
        unique: true
    })
    @Generated('uuid')
    id: string

    @Column({
        name: 'email',
        type: 'varchar',
        unique: true
    })
    email: string

    @Column({
        name: 'password',
        type: 'varchar'
    })
    password: string

    @Column({
        name: 'username',
        type: 'varchar'
    })
    username: string

    @Column({
        type: 'varchar'
    })
    avatarColor: string

    @Column({
        type: 'boolean',
        default: false
    })
    confirmed: boolean

    @Column({
        name: 'token',
        type: 'varchar',
        nullable: true
    })
    token?: string

}
<file_sep>/src/bl/refreshTokenFacade.ts
import { getRepository } from 'typeorm'

import { OAuth2RefreshToken } from '../entities/RefreshToken'

export class RefreshTokenFacade {

    static async getById(id: string) {
        return await getRepository(OAuth2RefreshToken).findOne({ 
            refreshToken: id 
        }).then(token => token!)
    }

}
<file_sep>/src/routes/github.ts
import * as express from 'express'
import * as rq from 'request'

import { config } from '../config'

import { GithubService } from '../util/oauth'
import { login } from '../util/login'
import { fullUrlFromString, API_HOST, AUTH_HOST } from '../util/url'

import { LoginController } from './login'
import { UserFacade } from '../bl/userFacade'
import { OAuth2User } from '../entities/User'

interface GithubProfile {
    login: string
    avatar_url: string
    id: number,
    name: string
}

interface GithubEmail {
    email: string
}

export class GithubController {

    static async getGithubLogin(req: express.Request, res: express.Response) {
        res.redirect(GithubService.getAuthorizeUrl({
            redirect_uri: config.github.callbackURL,
            scope: 'user:email'
        }))
    }

    static async getGithubLoginCallback(req: express.Request, res: express.Response) {
        const code = req.query.code
        GithubService.getOAuthAccessToken(code, {}, async (tokenErr, accessToken, refreshToken) => {
            if (tokenErr || !accessToken) {
                res.redirect(fullUrlFromString('/login?oauth_error=Github', AUTH_HOST))
            }

            try {
                const [ profile, emails ] = await Promise.all([
                    GithubController.getUserProfile(accessToken), 
                    GithubController.getUserEmails(accessToken)
                ])
                const primaryEmail = emails[0].email

                let user = await UserFacade.getByEmail(primaryEmail)

                if (!user) {
                    user = await GithubController.createUser(primaryEmail, profile.login)
                }

                await login(user!.uid, res)
                return LoginController.redirectLogin(req, res)
            } catch (e) {
                console.log(e)
                res.redirect(fullUrlFromString('/login?oauth_error=Github', AUTH_HOST))
            }
            res.end()
        })
    }

    private static createUser(email: string, username: string): Promise<OAuth2User> {
        return new Promise((resolve, reject) => {
            rq.post(
                fullUrlFromString('/register', API_HOST), 
                {
                    json: true,
                    body: {
                        email,
                        username
                    }
                },
                (reqErr, response, body) => {
                    if (reqErr) {
                        reject(reqErr)
                    } else {
                        if (response.statusCode === 400) {
                            reject(body)
                        }
                        body.uid = body.id
                        resolve(body)
                    }
                }
            )
        }) 
    }

    private static getUserProfile(accessToken: string): Promise<GithubProfile> {
        return new Promise((resolve, reject) => {
            rq.get(
                'https://api.github.com/user', 
                {
                    auth: {
                        bearer: accessToken
                    },
                    headers: {
                        'User-Agent': config.github.userAgent
                    } 
                },
                (reqErr, response, body) => {
                    if (reqErr) {
                        reject(reqErr)
                    } else {
                        resolve(JSON.parse(body))
                    }
                }
            )
        }) 
    }

    private static getUserEmails(accessToken: string): Promise<GithubEmail[]> {
        return new Promise((resolve, reject) => {
            rq.get(
                'https://api.github.com/user/emails', 
                {
                    auth: {
                        bearer: accessToken
                    },
                    headers: {
                        'User-Agent': config.github.userAgent
                    } 
                },
                (reqErr, response, body) => {
                    if (reqErr) {
                        reject(reqErr)
                    } else {
                        resolve(JSON.parse(body))
                    }
                }
            )
        }) 
    }

}
<file_sep>/src/routes/login.ts
import * as express from 'express'

import { config } from '../config'
import { fullUrlFromReq, fullUrlFromString, AUTH_HOST } from '../util/url'

import { login } from '../util/login'

import { UserFacade } from '../bl/userFacade'

export class LoginController {
    
    static getLogin(req: express.Request, res: express.Response) {
        if (req.cookies[config.loginCookieName]) {
            return LoginController.redirectLogin(req, res)
        }

        let errors: string[] = []
        if (req.query.oauth_error) {
            errors.push(`Error during ${req.query.oauth_error} authentication process`)
        }

        if (req.query.redirect) {
            res.cookie(config.redirectCookieName, req.query.redirect)
        }

        return res.render('login', { // views: login
            redirect_uri: fullUrlFromReq(req, AUTH_HOST),
            email: '',
            errors,
            github_login_url: fullUrlFromString('/github', AUTH_HOST)
        })
    }

    static async postLogin(req: express.Request, res: express.Response) {
        const user = await UserFacade.getByEmailAndPassword(req.body.email, req.body.password)
        if (user && user.confirmed) {
            await login(user.uid, res)
            return LoginController.redirectLogin(req, res)
        } else {
            return res.render('login', { // views: login
                redirect_uri: fullUrlFromReq(req, AUTH_HOST),
                email: req.body.email || '',
                errors: ['Invalid credentials or not confirmed account'],
                github_login_url: fullUrlFromString('/github', AUTH_HOST)
            })
        }
    }

    static getLogout(req: express.Request, res: express.Response) {
        req.logout()
        res.clearCookie(config.loginCookieName)
        res.redirect('/')
    }

    static redirectLogin(req: express.Request, res: express.Response) {
        let redirect =  req.cookies[config.redirectCookieName] || req.query.redirect
        if (redirect) {
            if (!redirect.startsWith('/')) {
                redirect = `/${redirect}`
            }
            return res.clearCookie(config.redirectCookieName).redirect(redirect)
        } else {
            return res.redirect(`/${config.loginDefaultRedirect}`)
        }
    }

}
<file_sep>/src/entities/Client.ts
import { Entity, Column, Generated, PrimaryGeneratedColumn } from 'typeorm'

@Entity('client')
export class OAuth2Client {

    @PrimaryGeneratedColumn({
        name: 'id'
    })
    cid: number

    @Column({
        name: 'client_id',
        type: 'varchar',
        unique: true
    })
    @Generated('uuid')
    id: string

    @Column({
        type: 'varchar'
    })
    name: string

    @Column({
        name: 'client_secret',
        type: 'varchar'
    })
    @Generated('uuid')
    clientSecret: string

    @Column({
        name: 'redirect_uris',
        type: 'simple-array'
    })
    redirectUris: string[]

    grants: string[] = ['authorization_code', 'refresh_token']

    @Column({
        name: 'is_trusted',
        type: 'boolean'
    })
    isTrusted: boolean
    
}
<file_sep>/src/entities/RefreshToken.ts
import { Entity, Column, PrimaryColumn, ManyToOne } from 'typeorm'

import { OAuth2Client } from './Client'
import { OAuth2User } from './User'

@Entity('oauth_refresh_token')
export class OAuth2RefreshToken {

    @PrimaryColumn({
        name: 'refresh_token',
        type: 'varchar'
    })
    refreshToken: string

    @Column({
        name: 'refresh_token_expires_at',
        type: 'date'
    })
    refreshTokenExpiresAt: Date

    @Column({
        name: 'scope',
        type: 'varchar',
        nullable: true
    })
    scope: string

    @ManyToOne(type => OAuth2User)
    user: OAuth2User

    @ManyToOne(type => OAuth2Client)
    client: OAuth2Client

}
<file_sep>/src/routes/callback.ts
import * as express from 'express'
import * as rq from 'request'

import { InternalOAuth, config } from '../config'

import { fullUrlFromString, AUTH_HOST } from '../util/url'

export class CallbackController {

    static async getZendeskToken(req: express.Request, res: express.Response) {
        try {
            const token = await CallbackController.retrieveAccessToken(req.query.code, config.zendesk)
            return res.status(200).json(token)
        } catch (e) {
            return res.status(400).json({ error: e })
        }
    }

    static async getElectronToken(req: express.Request, res: express.Response) {
        try {
            const token = await CallbackController.retrieveAccessToken(req.query.code, config.electron)
            return res.status(200).json(token)
        } catch (e) {
            return res.status(400).json({ error: e })
        }
    }

    private static retrieveAccessToken(code: string, client: InternalOAuth): Promise<{}> {
        return new Promise((resolve, reject) => {
            rq.post(
                fullUrlFromString(`/oauth/token` , AUTH_HOST), 
                {
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded'
                      },
                    body: `client_id=${client.clientId}`
                        + `&client_secret=${client.clientSecret}`
                        + `&grant_type=authorization_code&code=${code}`
                        + `&redirect_uri=${client.redirectUri}`
                },
                (reqErr, response, body) => {
                    if (reqErr) {
                        reject(reqErr)
                    } else {
                        if (response.statusCode === 200) {
                            resolve(JSON.parse(body))
                        } else {
                            reject(JSON.parse(body))
                        }
                    }
                }
            )
        }) 
    }

}
<file_sep>/src/config.ts
import { ConnectionOptions } from 'typeorm'

export interface GithubConfig {
    clientID: string
    clientSecret: string
    callbackURL: string
    userAgent: string
}

export interface ServerConfig {
    host: string
    port: number
    apiSuffix: string
    authSuffix: string
}

export interface InternalOAuth {
    clientId: string
    clientSecret: string
    redirectUri: string
}

export interface Config {
    env: 'development' | 'production' | 'test'

    loginCookieName: string
    loginDefaultRedirect: string
    internalToken: string
    redirectCookieName: string

    github: GithubConfig
    server: ServerConfig
    database: ConnectionOptions

    electron: InternalOAuth
    zendesk: InternalOAuth
}

export const config: Config = {
    env: process.env.NODE_ENV || 'development',
    
    loginCookieName: process.env.LOGIN_COOKIE_NAME || 'photon',
    loginDefaultRedirect: process.env.LOGIN_DEFAULT_REDIRECT || 'overview',
    internalToken: process.env.INTERNAL_TOKEN || '<PASSWORD>',
    redirectCookieName: 'redirect',

    github: {
        clientID: process.env.GITHUB_CLIENT_ID || '8515ee45647519a537bd',
        clientSecret: process.env.GITHUB_CLIENT_SECRET || '9399ae2120cab4a95d1890de32a8c64ab82dc19a',
        callbackURL: process.env.GITHUB_CALLBACK_URL || 'http://localhost/auth/github/callback',
        userAgent: process.env.GITHUB_CLIENT_NAME || 'Prello-dev'
    },

    server: {
        host: process.env.SERVER_HOST || 'http://localhost',
        port: process.env.PORT || 8000,
        apiSuffix: process.env.API_SUFFIX || 'api',
        authSuffix: process.env.AUTH_SUFFIX || 'auth'
    },

    database: {
        type: process.env.DATABASE_TYPE || 'postgres',
        ssl: process.env.DATABASE_SSL === 'true',
        url: process.env.DATABASE_URL || 'postgres://postgres:root@localhost:5434/dev_prello'
    },

    electron: {
        clientId: process.env.INTERNAL_OAUTH_ELECTRON_CLIENT_ID || 'e70919f4-b7f3-466b-b326-9faa7f7290f0',
        clientSecret: process.env.INTERNAL_OAUTH_ELECTRON_CLIENT_SECRET || '0927e397-8be8-4216-935b-53e3e4424261',
        redirectUri: process.env.INTERNAL_OAUTH_ELECTRON_CLIENT_REDIRECT_URI || 'http://localhost:3000/oauth'
    }

    zendesk: {
        clientId: process.env.INTERNAL_OAUTH_ZENDESK_CLIENT_ID || '9fc19d15-4a3a-4373-8f50-c1b478a8051b',
        clientSecret: process.env.INTERNAL_OAUTH_ZENDESK_CLIENT_SECRET || '0927e397-8be8-4216-935b-53e3e4424261',
        redirectUri: process.env.INTERNAL_OAUTH_ZENDESK_CLIENT_REDIRECT_URI || 'http://localhost:4567/oauth.html'
    }
}
<file_sep>/src/bl/clientFacade.ts
import { getRepository } from 'typeorm'

import { OAuth2Client } from '../entities/Client'

export class ClientFacade {

    static async getById(id: string) {
        return await getRepository(OAuth2Client).findOne({
            id
        })
    }

    static async getByIdAndSecret(id: string, secret: string) {
        if (secret) {
            return await getRepository(OAuth2Client).findOne({
                id,
                clientSecret: secret
            })         
        } else {
            return await ClientFacade.getById(id)
        }
    }

}
<file_sep>/src/bl/userFacade.ts
import { getRepository } from 'typeorm'
import { v4 as uuid } from 'uuid'
import { compareSync } from 'bcrypt'

import { OAuth2User } from '../entities/User'

export class UserFacade {

    static async getById(id: string) {
        return await getRepository(OAuth2User).findOne({
            id
        })
    }

    static async getByEmail(email: string) {
        return await getRepository(OAuth2User).findOne({
            email
        })
    }

    static async getByEmailAndPassword(email: string, password: string) {
        const user = await UserFacade.getByEmail(email)
        if (user && compareSync(password, user.password)) {
            return user
        } else {
            return false
        }
    }

    static async getByToken(token: string) {
        return await getRepository(OAuth2User).findOne({
            token
        })
    }

    static async authenticate(uid: number) {
        const token = uuid()
        await getRepository(OAuth2User).update({ uid }, { token })
        return token
    }

}
<file_sep>/src/util/url.ts
import * as express from 'express'

import { config } from '../config'

export const HOST = config.server.host
export const API_HOST = `${HOST}/${config.server.apiSuffix}`
export const AUTH_HOST = `${HOST}/${config.server.authSuffix}`

export const fullUrlFromString = (originalUrl: string, host: string) => {
    return `${host}${originalUrl}`
}

export const fullUrlFromReq = (req: express.Request, host: string) => {
    return fullUrlFromString(req.originalUrl, host)
}
<file_sep>/src/bl/accessTokenFacade.ts
import { getRepository } from 'typeorm'

import { Token } from 'oauth2-server'

import { OAuth2AccessToken } from '../entities/AccessToken'
import { OAuth2Client } from '../entities/Client'
import { OAuth2User } from '../entities/User'

export class AccessTokenFacade {

    static async getById(id: string) {
        return await getRepository(OAuth2AccessToken).findOne({ 
            relations: ['client', 'user'],
            where: {
                accessToken: id 
            }
        }).then(token => {
            if (token) {
                const localToken = token!
                localToken.accessTokenExpiresAt = new Date(localToken.accessTokenExpiresAt)
                return localToken
            }
            return token!
        })
    }

    static async getByClientAndUser(clientId: string, userId: string) {
        return await getRepository(OAuth2AccessToken)
            .createQueryBuilder('token')
            .leftJoin('token.user', 'user')
            .leftJoin('token.client', 'client')
            .where('user.id = :userId', { userId })
            .where('client.id = :clientId', { clientId })
            .getOne()
            .then(token => token!)
    }

    static async getAllByUser(userId: string) {
        return await getRepository(OAuth2AccessToken)
            .createQueryBuilder('token')
            .leftJoin('token.user', 'user')
            .leftJoin('token.client', 'client')
            .where('user.id = :userId', { userId })
            .select([
                'token.accessTokenExpiresAt', 
                'token.scope', 
                'client.id', 
                'client.name', 
                'client.cid', 
                'user.id', 
                'user.uid'
            ])
            .getMany()
    }

    static async create(token: Token, client: OAuth2Client, user: OAuth2User) {
        token.client = client
        token.user = user
        token.accessTokenExpiresAt = new Date(2048, 1)
        return await getRepository(OAuth2AccessToken).save(token)
    }

    // static async deleteByClientAndUser(clientId: string, userId: string) {
    //     return await getRepository(OAuth2AccessToken).save(token)
    // }

}
<file_sep>/src/app.ts
import * as express from 'express'
import * as bodyParser from 'body-parser'
import * as cookieParser from 'cookie-parser'
import * as cors from 'cors'

import { config } from './config'

import { authenticateMiddleware } from './util/oauthMiddlewares'

import { CallbackController } from './routes/callback'
import { GithubController } from './routes/github'
import { LoginController } from './routes/login'
import { OauthController } from './routes/oauth'
import { UserController } from './routes/user'

import passport, { ensureLogin } from './util/passport'
import { ensureInternal } from './util/internalMiddleware'

export const app = express()

// Set up Express and Oauth2-Node
app.set('port', config.server.port) 
app.engine('ejs', require('ejs-locals'))
app.set('views', `${__dirname}/views`)
app.set('view engine', 'ejs')
app.use(cookieParser())
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
app.use(passport.initialize())

// Define routes
app.get('/', (req, res) => {  
    res.json({ healthcheck: 'ok' })
})

app.get('/login', LoginController.getLogin)
app.post('/login', LoginController.postLogin)
app.get('/logout', LoginController.getLogout)

app.get('/github', GithubController.getGithubLogin)
app.get('/github/callback', GithubController.getGithubLoginCallback)

app.get('/me', ensureLogin(), UserController.getMe)
app.use('/myUser', cors())
app.get('/myUser', authenticateMiddleware, UserController.getMyUser)

app.get('/data/token/:token/:type', ensureInternal(), UserController.getTokenData)
app.get('/tokens', authenticateMiddleware, UserController.getTokens)
// app.get('/tokens', authenticateMiddleware, UserController.getTokens) get data about the requester
// app.delete('/users/tokens/:clientId', LoginController.getLogin) revoke the token

app.use('/token/zendesk', cors({
    origin: 'https://d3v-prello.zendesk.com'
}))
app.get('/token/zendesk', CallbackController.getZendeskToken)
app.use('/token/electron', cors({
    origin: 'http://localhost:3000'
}))
app.get('/token/electron', CallbackController.getElectronToken)

app.get('/oauth/authorize', ensureLogin({ redirect: true }), OauthController.getAuthorize)
app.post('/oauth/authorize', ensureLogin({ redirect: true }), OauthController.postAuthorize)
app.post('/oauth/token', OauthController.postToken)
<file_sep>/src/entities/AuthorizationCode.ts
import { Entity, Column, PrimaryColumn, ManyToOne } from 'typeorm'

import { OAuth2Client } from './Client'
import { OAuth2User } from './User'

@Entity('oauth_authorization_code')
export class OAuth2AuthorizationCode {

    @PrimaryColumn({
        name: 'authorization_code',
        type: 'varchar'
    })
    authorizationCode: string

    @Column({
        name: 'authorization_code_expires_at',
        type: 'date'
    })
    expiresAt: Date

    @Column({
        name: 'redirect_uri',
        type: 'varchar'
    })
    redirectUri: string

    @Column({
        name: 'scope',
        type: 'varchar',
        nullable: true
    })
    scope: string

    @ManyToOne(type => OAuth2User)
    user: OAuth2User

    @ManyToOne(type => OAuth2Client)
    client: OAuth2Client

}
<file_sep>/src/entities/AccessToken.ts
import { Entity, Column, PrimaryColumn, ManyToOne } from 'typeorm'

import { OAuth2Client } from './Client'
import { OAuth2User } from './User'

@Entity('oauth_access_token')
export class OAuth2AccessToken {

    @PrimaryColumn({
        name: 'access_token',
        type: 'varchar'
    })
    accessToken: string

    @Column({
        name: 'access_token_expires_at',
        type: 'date'
    })
    accessTokenExpiresAt: Date

    @Column({
        name: 'scope',
        type: 'varchar',
        nullable: true
    })
    scope: string

    @ManyToOne(type => OAuth2User)
    user: OAuth2User

    @ManyToOne(type => OAuth2Client)
    client: OAuth2Client

}
