diff --git a/packages/hoppscotch-backend/.vscode/settings.json b/packages/hoppscotch-backend/.vscode/settings.json index 4a70cc4e4..0967ef424 100644 --- a/packages/hoppscotch-backend/.vscode/settings.json +++ b/packages/hoppscotch-backend/.vscode/settings.json @@ -1,22 +1 @@ -{ - "workbench.colorCustomizations": { - "activityBar.activeBackground": "#93e6fc", - "activityBar.background": "#93e6fc", - "activityBar.foreground": "#15202b", - "activityBar.inactiveForeground": "#15202b99", - "activityBarBadge.background": "#fa45d4", - "activityBarBadge.foreground": "#15202b", - "commandCenter.border": "#15202b99", - "sash.hoverBorder": "#93e6fc", - "statusBar.background": "#61dafb", - "statusBar.foreground": "#15202b", - "statusBarItem.hoverBackground": "#2fcefa", - "statusBarItem.remoteBackground": "#61dafb", - "statusBarItem.remoteForeground": "#15202b", - "titleBar.activeBackground": "#61dafb", - "titleBar.activeForeground": "#15202b", - "titleBar.inactiveBackground": "#61dafb99", - "titleBar.inactiveForeground": "#15202b99" - }, - "peacock.remoteColor": "#61dafb" -} +{} diff --git a/packages/hoppscotch-backend/package.json b/packages/hoppscotch-backend/package.json index 786da1de3..ace0efd85 100644 --- a/packages/hoppscotch-backend/package.json +++ b/packages/hoppscotch-backend/package.json @@ -48,6 +48,7 @@ "passport-google-oauth20": "^2.0.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", + "passport-microsoft": "^1.0.0", "postmark": "^3.0.15", "prisma": "^4.7.1", "reflect-metadata": "^0.1.13", @@ -69,6 +70,7 @@ "@types/passport-github2": "^1.2.5", "@types/passport-google-oauth20": "^2.0.11", "@types/passport-jwt": "^3.0.8", + "@types/passport-microsoft": "^0.0.0", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", diff --git a/packages/hoppscotch-backend/pnpm-lock.yaml b/packages/hoppscotch-backend/pnpm-lock.yaml index a943bff3d..998336187 100644 --- a/packages/hoppscotch-backend/pnpm-lock.yaml +++ b/packages/hoppscotch-backend/pnpm-lock.yaml @@ -23,6 +23,7 @@ specifiers: '@types/passport-github2': ^1.2.5 '@types/passport-google-oauth20': ^2.0.11 '@types/passport-jwt': ^3.0.8 + '@types/passport-microsoft': ^0.0.0 '@types/supertest': ^2.0.12 '@typescript-eslint/eslint-plugin': ^5.45.0 '@typescript-eslint/parser': ^5.45.0 @@ -51,6 +52,7 @@ specifiers: passport-google-oauth20: ^2.0.0 passport-jwt: ^4.0.1 passport-local: ^1.0.0 + passport-microsoft: ^1.0.0 postmark: ^3.0.15 prettier: ^2.8.0 prisma: ^4.7.1 @@ -93,6 +95,7 @@ dependencies: passport-google-oauth20: 2.0.0 passport-jwt: 4.0.1 passport-local: 1.0.0 + passport-microsoft: 1.0.0 postmark: 3.0.15 prisma: 4.8.1 reflect-metadata: 0.1.13 @@ -114,6 +117,7 @@ devDependencies: '@types/passport-github2': 1.2.5 '@types/passport-google-oauth20': 2.0.11 '@types/passport-jwt': 3.0.8 + '@types/passport-microsoft': 0.0.0 '@types/supertest': 2.0.12 '@typescript-eslint/eslint-plugin': 5.48.1_3jon24igvnqaqexgwtxk6nkpse '@typescript-eslint/parser': 5.48.1_iukboom6ndih5an6iafl45j2fe @@ -1804,6 +1808,12 @@ packages: '@types/passport-strategy': 0.2.35 dev: true + /@types/passport-microsoft/0.0.0: + resolution: {integrity: sha512-jfkltRosn+P/+RoFMTl+mCyBTgPTFhjDEF832j7fmlYpuf+5yuzPLz7Rm5XMKN/Gqpro6myCyGPTuCc4yBQ2jQ==} + dependencies: + '@types/passport-oauth2': 1.4.11 + dev: true + /@types/passport-oauth2/1.4.11: resolution: {integrity: sha512-KUNwmGhe/3xPbjkzkPwwcPmyFwfyiSgtV1qOrPBLaU4i4q9GSCdAOyCbkFG0gUxAyEmYwqo9OAF/rjPjJ6ImdA==} dependencies: @@ -5114,6 +5124,14 @@ packages: passport-strategy: 1.0.0 dev: false + /passport-microsoft/1.0.0: + resolution: {integrity: sha512-L1JHeCbSObSZZXiG7jU2KoKie6nzZLwGt38HXz1GasKrsCQdOnf5kH8ltV4BWNUfBL2Pt1csWn1iuBSerprrcg==} + engines: {node: '>= 0.4.0'} + dependencies: + passport-oauth2: 1.6.1 + pkginfo: 0.4.1 + dev: false + /passport-oauth2/1.6.1: resolution: {integrity: sha512-ZbV43Hq9d/SBSYQ22GOiglFsjsD1YY/qdiptA+8ej+9C1dL1TVB+mBE5kDH/D4AJo50+2i8f4bx0vg4/yDDZCQ==} engines: {node: '>= 0.4.0'} @@ -5192,6 +5210,11 @@ packages: find-up: 4.1.0 dev: true + /pkginfo/0.4.1: + resolution: {integrity: sha512-8xCNE/aT/EXKenuMDZ+xTVwkT8gsoHN2z/Q29l80u0ppGEXVvsKRzNMbtKhg8LS8k1tJLAHHylf6p4VFmP6XUQ==} + engines: {node: '>= 0.4.0'} + dev: false + /pluralize/8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} diff --git a/packages/hoppscotch-backend/src/auth/auth.controller.ts b/packages/hoppscotch-backend/src/auth/auth.controller.ts index 0021e701a..cda681730 100644 --- a/packages/hoppscotch-backend/src/auth/auth.controller.ts +++ b/packages/hoppscotch-backend/src/auth/auth.controller.ts @@ -77,4 +77,23 @@ export class AuthController { if (E.isLeft(authTokens)) throwHTTPErr(authTokens.left); authCookieHandler(res, authTokens.right, true); } + + @Get('microsoft') + @UseGuards(AuthGuard('microsoft')) + async microsoftAuth(@Request() req) {} + + @Get('microsoft/callback') + @UseGuards(AuthGuard('microsoft')) + async microsoftAuthRedirect(@Request() req, @Res() res) { + const authTokens = await this.authService.generateAuthTokens(req.user.id); + if (E.isLeft(authTokens)) throwHTTPErr(authTokens.left); + authCookieHandler(res, authTokens.right, true); + } + + @Get('logout') + async logout(@Res() res: Response) { + res.clearCookie('access_token'); + res.clearCookie('refresh_token'); + return res.redirect(process.env.REDIRECT_URL); + } } diff --git a/packages/hoppscotch-backend/src/auth/auth.module.ts b/packages/hoppscotch-backend/src/auth/auth.module.ts index 5be9219f7..d7b1fdf92 100644 --- a/packages/hoppscotch-backend/src/auth/auth.module.ts +++ b/packages/hoppscotch-backend/src/auth/auth.module.ts @@ -10,6 +10,7 @@ import { JwtStrategy } from './strategies/jwt.strategy'; import { RTJwtStrategy } from './strategies/rt-jwt.strategy'; import { GoogleStrategy } from './strategies/google.strategy'; import { GithubStrategy } from './strategies/github.strategy'; +import { MicrosoftStrategy } from './strategies/microsoft.strategy'; @Module({ imports: [ @@ -27,6 +28,7 @@ import { GithubStrategy } from './strategies/github.strategy'; RTJwtStrategy, GoogleStrategy, GithubStrategy, + MicrosoftStrategy, ], controllers: [AuthController], }) diff --git a/packages/hoppscotch-backend/src/auth/strategies/github.strategy.ts b/packages/hoppscotch-backend/src/auth/strategies/github.strategy.ts index 4ca299cc3..d321548fa 100644 --- a/packages/hoppscotch-backend/src/auth/strategies/github.strategy.ts +++ b/packages/hoppscotch-backend/src/auth/strategies/github.strategy.ts @@ -15,10 +15,12 @@ export class GithubStrategy extends PassportStrategy(Strategy) { clientID: process.env.GITHUB_CLIENT_ID, clientSecret: process.env.GITHUB_CLIENT_SECRET, callbackURL: process.env.GITHUB_CALLBACK_URL, + scope: [process.env.GITHUB_SCOPE], }); } - async validate(accessToken, refreshToken, profile, done): Promise { + async validate(accessToken, refreshToken, profile, done) { + console.dir(profile); const user = await this.usersService.findUserByEmail( profile.emails[0].value, ); diff --git a/packages/hoppscotch-backend/src/auth/strategies/microsoft.strategy.ts b/packages/hoppscotch-backend/src/auth/strategies/microsoft.strategy.ts new file mode 100644 index 000000000..b34cc9335 --- /dev/null +++ b/packages/hoppscotch-backend/src/auth/strategies/microsoft.strategy.ts @@ -0,0 +1,50 @@ +import { Strategy } from 'passport-microsoft'; +import { PassportStrategy } from '@nestjs/passport'; +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { AuthService } from '../auth.service'; +import { UserService } from 'src/user/user.service'; +import * as O from 'fp-ts/Option'; + +@Injectable() +export class MicrosoftStrategy extends PassportStrategy(Strategy) { + constructor( + private authService: AuthService, + private usersService: UserService, + ) { + super({ + clientID: process.env.MICROSOFT_CLIENT_ID, + clientSecret: process.env.MICROSOFT_CLIENT_SECRET, + callbackURL: process.env.MICROSOFT_CALLBACK_URL, + scope: process.env.MICROSOFT_SCOPE, + }); + } + + async validate(accessToken, refreshToken, profile, done) { + const user = await this.usersService.findUserByEmail( + profile.emails[0].value, + ); + + if (O.isNone(user)) { + const createdUser = await this.usersService.createUserSSO( + accessToken, + refreshToken, + profile, + ); + return createdUser; + } + + // Check to see if entry for microsoft is present in the Account table for this user + const providerAccountExists = + await this.authService.checkIfProviderAccountExists(user.value, profile); + + if (O.isNone(providerAccountExists)) + await this.usersService.createProviderAccount( + user.value, + accessToken, + refreshToken, + profile, + ); + + return user.value; + } +}