Skip to content

Commit

Permalink
ft(auth): added bitbucket auth
Browse files Browse the repository at this point in the history
  • Loading branch information
thepatrickniyo committed Apr 27, 2022
1 parent 02c3681 commit 30f78ef
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 1 deletion.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
"module-alias": "^2.2.2",
"nodemon": "^2.0.15",
"passport": "^0.5.2",
"passport-atlassian-oauth2": "^2.1.0",
"passport-github2": "^0.1.12",
"passport-oauth2": "^1.6.1",
"passport-stackapps-ts": "^1.0.3",
"typescript-ioc": "^3.2.2"
},
Expand Down
2 changes: 2 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import express from 'express';
import session from 'express-session';
import passport from './strategies/passport.strategy';
import StackExchangeAuthRouter from './routes/auth/stack-exchange-auth.routes';
import BitBucketAuthRouter from './routes/auth/bitbucket.auth.routes';
import GithubAuthRouter from './routes/auth/github-auth.routes';
import { StackExchangeSearchRouter, GithubSearchRouter, BitBucketRouter} from './routes';
import { User } from 'User';
Expand All @@ -31,6 +32,7 @@ app.get('/', (req, res) => {
});
app.use(`${API_PREFIX}/auth/stack-exchange`, StackExchangeAuthRouter);
app.use(`${API_PREFIX}/auth/github`, GithubAuthRouter);
app.use(`${API_PREFIX}/auth/bitbucket`, BitBucketAuthRouter);
app.use(`${API_PREFIX}/stack-exchange/search`, StackExchangeSearchRouter);
app.use(`${API_PREFIX}/github/search`, GithubSearchRouter);
app.use(`${API_PREFIX}/bitbucket/search`, BitBucketRouter);
Expand Down
5 changes: 5 additions & 0 deletions src/middlewares/passport/passport-strategy-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@ export const GithubStrategyType = (req: any, _: Response, next: NextFunction) =
export const StackExchangeStrategyType = (req: any, _: Response, next: NextFunction) => {
req.passportStrategyType = 'stack-exchange';
next();
}

export const AtlassianStrategyType = (req: any, _: Response, next: NextFunction) => {
req.passportStrategyType = 'atlassian';
next();
}
14 changes: 14 additions & 0 deletions src/routes/auth/bitbucket.auth.routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Router } from 'express';
import { AtlassianStrategyType } from '../../middlewares/passport/passport-strategy-type';
import { login, logout } from '../../controllers/auth-controller';
import passport from '../../strategies/passport.strategy';

const router = Router();

router.get('/',passport.authenticate('atlassian'));

router.get('/callback', AtlassianStrategyType, login);

router.get('/logout', logout);

export default router;
1 change: 1 addition & 0 deletions src/routes/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './auth/stack-exchange-auth.routes';
export * from './auth/github-auth.routes';
export * from './auth/bitbucket.auth.routes';
export * from './stack-exchange/search.routes';
export * from './github/search.routes';
export * from './bitbucket/search.routes';
148 changes: 148 additions & 0 deletions src/strategies/atlassian.strategry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import OAuth2Strategy from 'passport-oauth2';

const defaultOptions = {
authorizationURL: 'https://auth.atlassian.com/authorize',
tokenURL: 'https://auth.atlassian.com/oauth/token',
profileURL: 'https://api.atlassian.com/me',
accessibleResourcesURL: 'https://api.atlassian.com/oauth/token/accessible-resources',
};

const defaultScope = ['read:me'];

class Strategy extends OAuth2Strategy {
/**
* `Strategy` constructor.
*
* Atlassian strategy authenticates requests using the OAuth 2.0 (3LO) protocol.
*
* Applications must supply a `verify` callback which accepts an `accessToken`,
* `refreshToken` and service-specific `profile`, and then calls the `cb`
* callback supplying a `user`, which should be set to `false` if thed
* credentials are not valid. If an exception occured, `err` should be set.
*
* Options:
* - `clientID` your Atlassian application's App ID
* - `clientSecret` your Atlassian application's App Secret
* - `callbackURL` URL to which Atlassian will redirect the user after granting authorization
* - `scope` API scopes that enable access to certain resources
*
* Examples:
*
* passport.use(new AtlassianStrategy({
* clientID: '123-456-789',
* clientSecret: 'secret',
* callbackURL: 'https://www.example.net/auth/atlassian/callback',
* scope: 'offline_access read:jira-user'
* }, function(accessToken, refreshToken, profile, cb) {
* User.findOrCreate(..., function (err, user) {
* cb(err, user);
* });
* }));
*
* @constructor
* @param {object} options
* @param {function} verify
* @access public
*/
private options;
public name='atlassian';
private _skipUserProfile:any;
private _scope:any;
private _scopeSeparator:any;


constructor(options:any = {}, verify:any) {
if (!options?.scope) {
throw new TypeError('AtlassianOAuth2 requires a scope option');
}

options = {
...defaultOptions,
...options,
};



super(options, verify);
this.options = options;
this.name = 'atlassian';
this._oauth2.useAuthorizationHeaderforGET(true);
this._setupDefaultScope();
}

/**
* Return extra Atlassian OAuth2 specific parameters to be included in the authorization
* request.
*
* @return {object}
* @access protected
*/
authorizationParams() {
return {
audience: 'api.atlassian.com',
prompt: 'consent',
};
}

/**
* Return normalized user profile
*
* @param {string} accessToken
* @param {function} done
* @access protected
*/
userProfile(accessToken:any, done:any) {
this._oauth2.get(this.options.profileURL, accessToken, (err:any, body:any) => {
if (err) return done(err);

let profile:any;

try {
const json = JSON.parse(body);
profile = this._convertProfileFields(json);
profile.provider = 'atlassian';
profile._raw = body;
profile._json = json;
} catch (e) {
return done(new Error('Failed to parse user profile'));
}

// Retreive list of resources user has access to
this._oauth2.get(this.options.accessibleResourcesURL, accessToken, (errAr:any, bodyAr:any) => {
if (errAr) return done(errAr);

try {
profile.accessibleResources = JSON.parse(bodyAr);
done(null, profile);
} catch (e) {
return done(new Error('Failed to parse accessible resources'));
}
});
});
}

_setupDefaultScope() {
if (this._skipUserProfile) {
return;
}

let scope = this._scope || [];

if (!Array.isArray(scope)) {
scope = scope.split(this._scopeSeparator);
}

this._scope = Array.from(new Set([...scope, ...defaultScope]));
}

_convertProfileFields(json:any) {
return {
id: json.account_id,
displayName: json.name,
emails: [{ value: json.email, verified: json.email_verified }],
photos: [{ value: json.picture }],
};
}
}

export default Strategy;
17 changes: 17 additions & 0 deletions src/strategies/passport.strategy.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import 'dotenv/config';
import passport from 'passport';
import AtlassianStrategy from './atlassian.strategry';
import { Strategy as StackExchangeStrategy } from 'passport-stackapps-ts';
import { Profile } from 'Profile';

const STACKEXCHANGE_CLIENT_ID = process.env.STACKEXCHANGE_CLIENT_ID as string;
const STACKEXCHANGE_CLIENT_SECRET = process.env.STACKEXCHANGE_CLIENT_SECRET as string;
const STACKEXCHANGE_APPS_KEY = process.env.STACKEXCHANGE_APPS_KEY as string;

const BITBUCKET_CLIENT_ID = process.env.BITBUCKET_CLIENT_ID as string;
const BITBUCKET_SECRET = process.env.BITBUCKET_SECRET as string;

passport.use(
new StackExchangeStrategy(
{
Expand All @@ -25,4 +29,17 @@ passport.use(
)
);

passport.use
(new AtlassianStrategy({
clientID: BITBUCKET_CLIENT_ID,
clientSecret: BITBUCKET_SECRET,
callbackURL: `http://${process.env.HOST}:${process.env.PORT}/api/v1/auth/bitbucket/callback`,
scope: 'offline_access read:jira-user read:bitbucket-user',
},
function (accessToken:string, refreshToken:string, profile: unknown, cb: unknown, done:any) {
process.nextTick(function () {
return done(null, { profile, accessToken, refreshToken });
});
}
));
export default passport;
3 changes: 2 additions & 1 deletion src/types/modules.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
declare module 'passport';
declare module 'passport-oauth'
declare module 'passport-oauth';
// declare module 'passport-atlassian-oauth2';

0 comments on commit 30f78ef

Please sign in to comment.