src/index.js
'use strict';
import uri from 'url';
import { OAuth2Strategy, InternalOAuthError } from 'passport-oauth';
import v1parser from './parsers/v1.profile.parsing';
import v2parser from './parsers/v2.profile.parsing';
export default class BitbucketTokenStrategy extends OAuth2Strategy {
constructor(_options, _verify) {
const options = _options || {};
const verify = _verify;
options.authorizationURL = 'https://bitbucket.org/site/oauth2/authorize';
options.tokenURL = 'https://bitbucket.org/site/oauth2/access_token';
super(options, verify);
this.name = 'bitbucket-token';
this._apiVersion = options.apiVersion || '1.0';
this._profileWithEmail = options.profileWithEmail || false;
this._accessTokenField = options.accessTokenField || 'access_token';
this._refreshTokenField = options.refreshTokenField || 'refresh_token';
this._profileURL = `https://api.bitbucket.org/${this._apiVersion}/user`;
this._clientSecret = options.clientSecret;
this._passReqToCallback = options.passReqToCallback || false;
this._oauth2.useAuthorizationHeaderforGET(true);
}
authenticate(req, options) {
const accessToken = this.lookup(req, this._accessTokenField);
const refreshToken = this.lookup(req, this._refreshTokenField);
if (!accessToken) return this.fail({message: `You should provide ${this._accessTokenField}`});
this._loadUserProfile(accessToken, (error, profile) => {
if (error) return this.error(error);
const verified = (error, user, info) => {
if (error) return this.error(error);
if (!user) return this.fail(info);
return this.success(user, info);
};
if (this._passReqToCallback) {
this._verify(req, accessToken, refreshToken, profile, verified);
} else {
this._verify(accessToken, refreshToken, profile, verified);
}
});
}
loadUserMail(accessToken, accountName, profile, done) {
const emailUrlPath = this.isApiV1() ? `https://api.bitbucket.org/1.0/users/${accountName}/emails` : 'https://api.bitbucket.org/2.0/user/emails';
const emailUrl = uri.parse(emailUrlPath);
this._oauth2.get(emailUrl, accessToken, (error, body, res) => {
if (error) return done(new InternalOAuthError('Failed to fetch user profile', error));
try {
const json = JSON.parse(body);
profile.emails = this.isApiV1() ? v1parser.emails(json) : v2parser.emails(json);
done(null, profile);
} catch (e) {
done(e);
}
});
}
isApiV1() {
return this._apiVersion === '1.0';
}
userProfile(accessToken, done) {
let profileURL = uri.parse(this._profileURL);
this._oauth2.get(profileURL, accessToken, (error, body, res) => {
if (error) return done(new InternalOAuthError('Failed to fetch user profile', error));
try {
const json = JSON.parse(body);
const profile = this.isApiV1() ? v1parser.profile(json, body) : v2parser.profile(json, body);
if (this._profileWithEmail) {
return this.loadUserMail(accessToken, profile.username, profile, done);
}
return done(null, profile);
} catch (e) {
return done(e);
}
});
}
parseOAuth2Token(req) {
const OAuth2AuthorizationField = 'Authorization';
const headerValue = (req.headers && (req.headers[OAuth2AuthorizationField] || req.headers[OAuth2AuthorizationField.toLowerCase()]));
return (
headerValue && (() => {
const bearerRE = /Bearer\ (.*)/;
let match = bearerRE.exec(headerValue);
return (match && match[1]);
})()
);
}
lookup(req, field) {
return (
(req.body && req.body[field]) ||
(req.query && req.query[field]) ||
(req.headers && (req.headers[field] || req.headers[field.toLowerCase()])) ||
this.parseOAuth2Token(req)
);
}
}