UNPKG

@mvx/identity

Version:

identity is oidc for mvc, type-mvc is base on koa. Decorator, Ioc, AOP mvc framework on server.

220 lines (218 loc) 8.23 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.OAuth2 = exports.OAuth2Error = void 0; const http = require("http"); const https = require("https"); const querystring = require("querystring"); const url_1 = require("url"); const errors_1 = require("../errors"); /** * oauth2 error. * * @export * @class OAuth2Error * @extends {AuthenticationError} */ class OAuth2Error extends errors_1.AuthenticationError { // tslint:disable-next-line:variable-name constructor(status, message, error_description) { super(status, message, error_description); } static ρAnn() { return { "name": "OAuth2Error" }; } } exports.OAuth2Error = OAuth2Error; /** * oauth2 * * @export * @class OAuth2 */ class OAuth2 { constructor(clientId, clientSecret, baseSite, authorizeUrl = '/oauth/authorize', accessTokenUrl = '/oauth/access_token', customHeader = {}) { this.clientId = clientId; this.clientSecret = clientSecret; this.baseSite = baseSite; this.authorizeUrl = authorizeUrl; this.accessTokenUrl = accessTokenUrl; this.customHeader = customHeader; this.accessTokenName = 'access_token'; this.authMethod = 'Bearer'; this.useAuthorizationHeaderForGET = false; this.agent = undefined; } // Allows you to set an agent to use instead of the default HTTP or // HTTPS agents. Useful when dealing with your own certificates. set Agent(agent) { this.agent = agent; } get Agent() { return this.agent; } // This 'hack' method is required for sites that don't use // 'access_token' as the name of the access token (for requests). // ( http://tools.ietf.org/html/draft-ietf-oauth-v2-16#section-7 ) // it isn't clear what the correct value should be atm, so allowing // for specific (temporary?) override for now. set AccessTokenName(name) { this.accessTokenName = name; } get AccessTokenName() { return this.accessTokenName; } // Sets the authorization method for Authorization header. // e.g. Authorization: Bearer <token> # "Bearer" is the authorization method. set AuthMethod(authMethod) { this.authMethod = authMethod; } get AuthMethod() { return this.authMethod; } // If you use the OAuth2 exposed 'get' method (and don't construct your own _request call ) // this will specify whether to use an 'Authorize' header instead of passing the access_token as a query parameter set UseAuthorizationHeaderForGET(useIt) { this.useAuthorizationHeaderForGET = useIt; } get UseAuthorizationHeaderForGET() { return this.useAuthorizationHeaderForGET; } get ClientId() { return this.clientId; } get AuthorizeUrl() { return this.authorizeUrl; } get AccessTokenUrl() { return `${this.baseSite}${this.accessTokenUrl}`; /* + "?" + querystring.stringify(params); */ } // Build the authorization header. In particular, build the part after the colon. // e.g. Authorization: Bearer <token> # Build "Bearer <token>" buildAuthHeader(token) { return `${this.authMethod} ${token}`; } getAuthorizeUrl(params = {}) { params.client_id = this.clientId; return `${this.baseSite}${this.authorizeUrl}?${querystring.stringify(params)}`; } async getOAuthAccessToken(code, params = {}) { params.client_id = this.clientId; params.client_secret = this.clientSecret; const codeParam = (params.grant_type === 'refresh_token') ? 'refresh_token' : 'code'; params[codeParam] = code; const postData = querystring.stringify(params); const postHeaders = { 'Content-Type': 'application/x-www-form-urlencoded', }; const { result } = await this.request('POST', this.AccessTokenUrl, postHeaders, postData, null); let data; try { // As of http://tools.ietf.org/html/draft-ietf-oauth-v2-07 // responses should be in JSON data = JSON.parse(result); } catch (error) { // .... However both Facebook + Github currently use rev05 of the spec // and neither seem to specify a content-type correctly in their response headers :( // clients of these services will suffer a *minor* performance cost of the exception // being thrown data = querystring.parse(result); } const accessToken = data[this.AccessTokenName]; if (!accessToken) { throw new OAuth2Error(400, JSON.stringify(params)); } const refreshToken = data.refresh_token; delete data.refresh_token; return { accessToken, refreshToken, result: data, }; } async get(url, accessToken) { let headers = {}; if (this.useAuthorizationHeaderForGET) { headers = { Authorization: this.buildAuthHeader(accessToken) }; accessToken = null; } return await this.request('GET', url, headers, '', accessToken); } request(method, url, headers = {}, postBody, accessToken) { const parsedUrl = url_1.parse(url, true); if (parsedUrl.protocol === 'https:' && !parsedUrl.port) { parsedUrl.port = '443'; } const realHeaders = Object.assign({}, this.customHeader, headers); realHeaders.Host = parsedUrl.host; if (!realHeaders['User-Agent']) { realHeaders['User-Agent'] = 'Node-oauth'; } realHeaders['Content-Length'] = 0; if (postBody) { realHeaders['Content-Length'] = Buffer.isBuffer(postBody) ? postBody.length : Buffer.byteLength(postBody); } if (accessToken && !('Authorization' in realHeaders)) { // It seems that the default value of .query return by URL.parse is {}. // if (!parsedUrl.query) { // parsedUrl.query = {}; // } parsedUrl.query[this.accessTokenName] = accessToken; } let queryStr = querystring.stringify(parsedUrl.query); if (queryStr) { queryStr = '?' + queryStr; } const options = { protocol: parsedUrl.protocol, host: parsedUrl.hostname, port: parsedUrl.port, path: parsedUrl.pathname + queryStr, method, headers: realHeaders, }; return new Promise((resolve, reject) => { this.executeRequest(options, postBody, (err, result, response) => err ? reject(err) : resolve({ result, response })); }); } executeRequest(options, postBody, callback) { let callbackCalled = false; let result = ''; // set the agent on the request options if (this.agent) { options.agent = this.agent; } const request = options.protocol !== 'https:' ? http.request(options) : https.request(options); request.on('response', (response) => { response.on('data', (chunk) => { result += chunk; }); response.addListener('end', () => { if (!callbackCalled) { callbackCalled = true; if (!(response.statusCode >= 200 && response.statusCode <= 299) && (response.statusCode !== 301) && (response.statusCode !== 302)) { callback(new OAuth2Error(response.statusCode, result)); } else { callback(null, result, response); } } }); }); request.on('error', (e) => { callbackCalled = true; callback(e); }); if ((options.method === 'POST' || options.method === 'PUT') && postBody) { request.write(postBody); } request.end(); } static ρAnn() { return { "name": "OAuth2" }; } } exports.OAuth2 = OAuth2; //# sourceMappingURL=../sourcemaps/passports/oauth2.js.map