import * as OAuthServer from "oauth2-server";
import * as winston from "winston";
import * as express from "express";
import { TokenUser, Base } from "openflow-api";
import { Config } from "./Config";
import { Crypt } from "./Crypt";
const Request = OAuthServer.Request;
const Response = OAuthServer.Response;
export class OAuthProvider {
private _logger: winston.Logger;
private app: express.Express;
public static instance: OAuthProvider = null;
// private clients = [{
// id: 'application', // TODO: Needed by refresh_token grant, because there is a bug at line 103 in https://github.com/oauthjs/node-oauth2-server/blob/v3.0.1/lib/grant-types/refresh-token-grant-type.js (used client.id instead of client.clientId)
// clientId: 'application',
// clientSecret: 'secret',
// grants: [
// 'password',
// 'refresh_token',
// 'authorization_code'
// ],
// redirectUris: []
// }];
private clients = [];
private tokens = [];
private codes = {};
public oauthServer: any = null;
private authorizationCodeStore: any = {};
static configure(logger: winston.Logger, app: express.Express): OAuthProvider {
const instance = new OAuthProvider();
try {
OAuthProvider.instance = instance;
instance._logger = logger;
instance.app = app;
instance.oauthServer = new OAuthServer({
model: instance,
grants: ['authorization_code', 'refresh_token'],
accessTokenLifetime: 60 * 60 * 24, // 24 hours, or 1 day
allowEmptyState: true,
allowExtendedTokenAttributes: true
});
(app as any).oauth = instance.oauthServer;
app.all('/oauth/token', instance.obtainToken.bind(instance));
app.get('/oauth/login', async (req, res) => {
instance.clients = await Config.db.query({ _type: "oauthclient" }, null, 10, 0, null, "config", Crypt.rootToken());
if (instance.clients == null || instance.clients.length == 0) return res.status(500).json({ message: 'OAuth not configured' });
let state = req.params.state;
if (state == null) state = encodeURIComponent(req.query.state as any);
const access_type = req.query.access_type;
const client_id = req.query.client_id;
const redirect_uri = req.query.redirect_uri;
const response_type = req.query.response_type;
const scope = req.query.scope;
let client = instance.getClientById(client_id);
if (req.user) {
if (client.redirectUris.length > 0) {
if (client.redirectUris.indexOf(redirect_uri) == -1) {
return res.status(500).json({ message: 'illegal redirect_uri ' + redirect_uri });
// client.redirectUris.push(redirect_uri);
}
}
const code = Math.random().toString(36).substr(2, 9);
instance._logger.info("[OAuth][" + (req.user as any).username + "] /oauth/login " + state);
instance.codes[code] = req.user;
instance.codes[code].redirect_uri = redirect_uri;
instance.codes[code].client_id = client_id;
res.redirect(`${redirect_uri}?state=${state}&code=${code}`);
} else {
instance._logger.info("[OAuth][anon] /oauth/login " + state);
res.cookie("originalUrl", req.originalUrl, { maxAge: 900000, httpOnly: true });
res.redirect("/login");
}
});
// app.get('/oauth/authorize', instance.authorize.bind(instance));
app.all('/oauth/authorize', (req, res) => {
const request = new Request(req);
const response = new Response(res);
return instance.oauthServer.authenticate(request, response)
.then((token) => {
res.json(token.user);
}).catch((err) => {
console.error(err);
res.status(err.code || 500).json(err);
});
});
// app.all('/oauth/authorize', instance.oauthServer.authenticate.bind(instance));
// app.all('/oauth/authorize/emails', instance.oauthServer.authenticate.bind(instance));
} catch (error) {
console.error(error);
const json = JSON.stringify(error, null, 3);
console.error(json);
throw error;
}
return instance;
}
authorize(req, res) {
this._logger.info("[OAuth] authorize");
const request = new Request(req);
const response = new Response(res);
console.log(request.headers);
return this.oauthServer.authorize(request, response)
.then((token) => {
res.json(token);
}).catch((err) => {
console.error(err);
res.status(err.code || 500).json(err);
});
}
authenticateHandler() {
return {
handle: (request, response) => {
//in this example, we store the logged-in user as the 'loginUser' attribute in session
if (request.session.loginUser) {
return { username: request.session.loginUser.username };
}
return null;
}
};
}
obtainToken(req, res) {
this._logger.info("[OAuth] obtainToken");
const request = new Request(req);
const response = new Response(res);
return this.oauthServer.token(request, response)
.then((token) => {
this._logger.info("[OAuth] obtainToken::success: token:");
res.json(token);
}).catch((err) => {
this._logger.info("[OAuth] obtainToken::failed: token:");
console.error(err);
res.status(err.code || 500).json(err);
});
}
public async getAccessToken(bearerToken) {
this._logger.info("[OAuth] getAccessToken " + bearerToken);
const tokens = await Config.db.query({ _type: "token", "accessToken": bearerToken }, null, 10, 0, null, "oauthtokens", Crypt.rootToken());
return tokens.length ? tokens[0] : false;
}
public async getRefreshToken(bearerToken) {
this._logger.info("[OAuth] getRefreshToken " + bearerToken);
const tokens = await Config.db.query({ _type: "token", "refreshToken": bearerToken }, null, 10, 0, null, "oauthtokens", Crypt.rootToken());
return tokens.length ? tokens[0] : false;
}
public getClient(clientId, clientSecret) {
this._logger.info("[OAuth] getClient " + clientId);
const clients = this.clients.filter((client) => {
return client.clientId === clientId && client.clientSecret === clientSecret;
});
return clients.length ? clients[0] : false;
}
public getClientById(clientId) {
this._logger.info("[OAuth] getClientById " + clientId);
const clients = this.clients.filter((client) => {
return client.clientId === clientId;
});
return clients.length ? clients[0] : false;
}
public async saveToken(token, client, user) {
this._logger.info("[OAuth] saveToken " + token);
const result: any = {
accessToken: token.accessToken,
access_token: token.accessToken,
accessTokenExpiresAt: token.accessTokenExpiresAt,
clientId: client.clientId,
refreshToken: token.refreshToken,
refresh_token: token.refreshToken,
refreshTokenExpiresAt: token.refreshTokenExpiresAt,
userId: user.id,
user: user,
client: client,
_type: "token"
};
this.tokens.push(result);
await Config.db.InsertOne(result, "oauthtokens", 0, false, Crypt.rootToken());
return result;
}
saveAuthorizationCode(code, client, user) {
this._logger.info("[OAuth] saveAuthorizationCode " + code);
// // const codeToSave: any = this.codes[code];
// const codeToSave: any = {
// 'authorizationCode': code.authorizationCode,
// 'expiresAt': code.expiresAt,
// 'redirectUri': code.redirectUri,
// 'scope': code.scope,
// 'client': client.id,
// 'user': user.username
// };
// this.codes[code] = codeToSave;
// this.revokeAuthorizationCode(code);
// code = Object.assign({}, code, {
// 'client': client.id,
// 'user': user.username
// });
return code;
}
getAuthorizationCode(code) {
this._logger.info("[OAuth] getAuthorizationCode " + code);
let user: TokenUser = this.codes[code];
const client_id: string = this.codes[code].client_id;
if (user == null) return null;
this.revokeAuthorizationCode(code);
const redirect_uri = (user as any).redirect_uri;
const expiresAt = new Date();
expiresAt.setMonth(expiresAt.getMonth() + 1);
user = TokenUser.From(user);
let client = this.getClientById(client_id);
let role = client.defaultrole;
const keys: string[] = Object.keys(client.rolemappings);
for (let i = 0; i < keys.length; i++) {
if (user.HasRoleName(keys[i])) role = client.rolemappings[keys[i]];
}
const result = {
code: code,
client: this.clients[0],
user: {
id: user._id,
_id: user._id,
name: user.name,
username: user.username,
email: user.username,
role: role
},
expiresAt: expiresAt,
redirectUri: redirect_uri
}
return result;
}
revokeAuthorizationCode(code) {
this._logger.info("[OAuth] revokeAuthorizationCode " + code);
delete this.codes[code];
return true;
// const user: TokenUser = this.codes[code];
// if (user != null) delete this.codes[code];
// return code;
}
}