Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
c00548f
chore: German `i18n` string entry additions (#5142)
pavog Jun 12, 2025
bf9e6c4
chore: merge `hoppscotch/main` into `hoppscotch/next`
jamesgeorge007 Jun 13, 2025
b2de105
chore: merge `hoppscotch/main` into `hoppscotch/next`
jamesgeorge007 Jun 19, 2025
929bdcc
chore: merge `hoppscotch/main` into `hoppscotch/next`
jamesgeorge007 Jun 20, 2025
5ece482
fix(desktop): prevent navigation on file drops (#5176)
CuriousCorrelation Jun 24, 2025
aa5b540
chore: merge `hoppscotch/main` into `hoppscotch/next`
jamesgeorge007 Jun 24, 2025
78e623a
feat(desktop): native tab keyboard shortcuts (#5190)
CuriousCorrelation Jun 24, 2025
594f078
fix: output raw numbers in JSON filter (#5152)
g3Bg2 Jun 24, 2025
427a181
chore: resolve global env in team env and tooltip UI update (#5187)
nivedin Jun 25, 2025
19362a4
fix(common): auto unescape utf-8 issue in the request body (#5185)
anwarulislam Jun 25, 2025
08e5fa9
refactor(backend): enhance auth strategies with type safety and bette…
Krishprajapati15 Jun 25, 2025
7952dbf
feat: ability to copy initial and current env value to eachother (#5195)
nivedin Jun 25, 2025
d674a2e
chore: bump version to `2025.6.0`
jamesgeorge007 Jun 25, 2025
0d4df74
chore: align `TypeScript` version across packages
jamesgeorge007 Jun 25, 2025
78a165d
chore: security patches for the dependency chain (#5196)
AndrewBastin Jun 25, 2025
965e4e1
feat(common): authentication strategy improvements (#5130)
anwarulislam Jun 25, 2025
b1d4ad2
chore(desktop): update `typescript` bump hash (#5199)
CuriousCorrelation Jun 26, 2025
224525b
fix(desktop): redirect menu bar events to window (#5200)
CuriousCorrelation Jun 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions devenv.nix
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ in {
nodePackages.prisma
prisma-engines
cargo-edit
cargo-tauri
] ++ lib.optionals pkgs.stdenv.isDarwin darwinPackages
++ lib.optionals pkgs.stdenv.isLinux linuxPackages;

Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@
"@babel/runtime@<7.26.10": "7.26.10",
"apiconnect-wsdl": "2.0.36",
"@xmldom/xmldom": "0.8.10",
"[email protected]": "2.0.0"
"[email protected]": "2.0.1",
"[email protected]": "2.0.2",
"[email protected]": "1.1.12"
},
"packageExtensions": {
"@hoppscotch/httpsnippet": {
Expand Down
2 changes: 1 addition & 1 deletion packages/codemirror-lang-graphql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@
"mocha": "9.2.2",
"rollup": "3.29.4",
"@rollup/plugin-typescript": "12.1.1",
"typescript": "5.2.2"
"typescript": "5.8.3"
}
}
2 changes: 1 addition & 1 deletion packages/hoppscotch-agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"autoprefixer": "^10.4.20",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.13",
"typescript": "^5.6.3",
"typescript": "^5.8.3",
"unplugin-icons": "^0.19.3",
"unplugin-vue-components": "28.4.1",
"vite": "^5.4.8",
Expand Down
2 changes: 1 addition & 1 deletion packages/hoppscotch-backend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hoppscotch-backend",
"version": "2025.5.4",
"version": "2025.6.0",
"description": "",
"author": "",
"private": true,
Expand Down
34 changes: 22 additions & 12 deletions packages/hoppscotch-backend/src/auth/strategies/github.strategy.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Strategy } from 'passport-github2';
import { Strategy, Profile } from 'passport-github2';
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';
import * as E from 'fp-ts/Either';
import { ConfigService } from '@nestjs/config';
import { validateEmail } from 'src/utils';
import { AUTH_EMAIL_NOT_PROVIDED_BY_OAUTH } from 'src/errors';

@Injectable()
export class GithubStrategy extends PassportStrategy(Strategy) {
Expand All @@ -15,18 +17,26 @@ export class GithubStrategy extends PassportStrategy(Strategy) {
private configService: ConfigService,
) {
super({
clientID: configService.get('INFRA.GITHUB_CLIENT_ID'),
clientSecret: configService.get('INFRA.GITHUB_CLIENT_SECRET'),
callbackURL: configService.get('INFRA.GITHUB_CALLBACK_URL'),
scope: [configService.get('INFRA.GITHUB_SCOPE')],
clientID: configService.get<string>('INFRA.GITHUB_CLIENT_ID'),
clientSecret: configService.get<string>('INFRA.GITHUB_CLIENT_SECRET'),
callbackURL: configService.get<string>('INFRA.GITHUB_CALLBACK_URL'),
scope: [configService.get<string>('INFRA.GITHUB_SCOPE')],
store: true,
});
}

async validate(accessToken, refreshToken, profile, done) {
const user = await this.usersService.findUserByEmail(
profile.emails[0].value,
);
async validate(
accessToken: string,
refreshToken: string,
profile: Profile,
done,
) {
const email = profile.emails?.[0].value;

if (!validateEmail(email))
throw new UnauthorizedException(AUTH_EMAIL_NOT_PROVIDED_BY_OAUTH);

const user = await this.usersService.findUserByEmail(email);

if (O.isNone(user)) {
const createdUser = await this.usersService.createUserSSO(
Expand All @@ -38,7 +48,7 @@ export class GithubStrategy extends PassportStrategy(Strategy) {
}

/**
* * displayName and photoURL maybe null if user logged-in via magic-link before SSO
* displayName and photoURL maybe null if user logged-in via magic-link before SSO
*/
if (!user.value.displayName || !user.value.photoURL) {
const updatedUser = await this.usersService.updateUserDetails(
Expand All @@ -51,8 +61,8 @@ export class GithubStrategy extends PassportStrategy(Strategy) {
}

/**
* * Check to see if entry for Github is present in the Account table for user
* * If user was created with another provider findUserByEmail may return true
* Check to see if entry for Github is present in the Account table for user
* If user was created with another provider findUserByEmail may return true
*/
const providerAccountExists =
await this.authService.checkIfProviderAccountExists(user.value, profile);
Expand Down
34 changes: 20 additions & 14 deletions packages/hoppscotch-backend/src/auth/strategies/google.strategy.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { Strategy, VerifyCallback, Profile } from 'passport-google-oauth20';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { UserService } from 'src/user/user.service';
import * as O from 'fp-ts/Option';
import { AuthService } from '../auth.service';
import * as E from 'fp-ts/Either';
import { ConfigService } from '@nestjs/config';
import { Request } from 'express';
import { validateEmail } from 'src/utils';
import { AUTH_EMAIL_NOT_PROVIDED_BY_OAUTH } from 'src/errors';

@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy) {
Expand All @@ -15,25 +18,28 @@ export class GoogleStrategy extends PassportStrategy(Strategy) {
private configService: ConfigService,
) {
super({
clientID: configService.get('INFRA.GOOGLE_CLIENT_ID'),
clientSecret: configService.get('INFRA.GOOGLE_CLIENT_SECRET'),
callbackURL: configService.get('INFRA.GOOGLE_CALLBACK_URL'),
scope: configService.get('INFRA.GOOGLE_SCOPE').split(','),
clientID: configService.get<string>('INFRA.GOOGLE_CLIENT_ID'),
clientSecret: configService.get<string>('INFRA.GOOGLE_CLIENT_SECRET'),
callbackURL: configService.get<string>('INFRA.GOOGLE_CALLBACK_URL'),
scope: configService.get<string>('INFRA.GOOGLE_SCOPE').split(','),
passReqToCallback: true,
store: true,
});
}

async validate(
req: Request,
accessToken,
refreshToken,
profile,
accessToken: string,
refreshToken: string,
profile: Profile,
done: VerifyCallback,
) {
const user = await this.usersService.findUserByEmail(
profile.emails[0].value,
);
const email = profile.emails?.[0].value;

if (!validateEmail(email))
throw new UnauthorizedException(AUTH_EMAIL_NOT_PROVIDED_BY_OAUTH);

const user = await this.usersService.findUserByEmail(email);

if (O.isNone(user)) {
const createdUser = await this.usersService.createUserSSO(
Expand All @@ -45,7 +51,7 @@ export class GoogleStrategy extends PassportStrategy(Strategy) {
}

/**
* * displayName and photoURL maybe null if user logged-in via magic-link before SSO
* displayName and photoURL maybe null if user logged-in via magic-link before SSO
*/
if (!user.value.displayName || !user.value.photoURL) {
const updatedUser = await this.usersService.updateUserDetails(
Expand All @@ -58,8 +64,8 @@ export class GoogleStrategy extends PassportStrategy(Strategy) {
}

/**
* * Check to see if entry for Google is present in the Account table for user
* * If user was created with another provider findUserByEmail may return true
* Check to see if entry for Google is present in the Account table for user
* If user was created with another provider findUserByEmail may return true
*/
const providerAccountExists =
await this.authService.checkIfProviderAccountExists(user.value, profile);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
),
),
]),
secretOrKey: configService.get('JWT_SECRET'),
secretOrKey: configService.get<string>('JWT_SECRET'),
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { UserService } from 'src/user/user.service';
import * as O from 'fp-ts/Option';
import * as E from 'fp-ts/Either';
import { ConfigService } from '@nestjs/config';
import { validateEmail } from 'src/utils';
import { AUTH_EMAIL_NOT_PROVIDED_BY_OAUTH } from 'src/errors';

@Injectable()
export class MicrosoftStrategy extends PassportStrategy(Strategy) {
Expand All @@ -15,19 +17,27 @@ export class MicrosoftStrategy extends PassportStrategy(Strategy) {
private configService: ConfigService,
) {
super({
clientID: configService.get('INFRA.MICROSOFT_CLIENT_ID'),
clientSecret: configService.get('INFRA.MICROSOFT_CLIENT_SECRET'),
callbackURL: configService.get('INFRA.MICROSOFT_CALLBACK_URL'),
scope: configService.get('INFRA.MICROSOFT_SCOPE').split(','),
tenant: configService.get('INFRA.MICROSOFT_TENANT'),
clientID: configService.get<string>('INFRA.MICROSOFT_CLIENT_ID'),
clientSecret: configService.get<string>('INFRA.MICROSOFT_CLIENT_SECRET'),
callbackURL: configService.get<string>('INFRA.MICROSOFT_CALLBACK_URL'),
scope: configService.get<string>('INFRA.MICROSOFT_SCOPE').split(','),
tenant: configService.get<string>('INFRA.MICROSOFT_TENANT'),
store: true,
});
}

async validate(accessToken: string, refreshToken: string, profile, done) {
const user = await this.usersService.findUserByEmail(
profile.emails[0].value,
);
async validate(
accessToken: string,
refreshToken: string,
profile,
done,
) {
const email = profile?.emails?.[0]?.value;

if (!validateEmail(email))
throw new UnauthorizedException(AUTH_EMAIL_NOT_PROVIDED_BY_OAUTH);

const user = await this.usersService.findUserByEmail(email);

if (O.isNone(user)) {
const createdUser = await this.usersService.createUserSSO(
Expand All @@ -39,7 +49,7 @@ export class MicrosoftStrategy extends PassportStrategy(Strategy) {
}

/**
* * displayName and photoURL maybe null if user logged-in via magic-link before SSO
* displayName and photoURL maybe null if user logged-in via magic-link before SSO
*/
if (!user.value.displayName || !user.value.photoURL) {
const updatedUser = await this.usersService.updateUserDetails(
Expand All @@ -52,8 +62,8 @@ export class MicrosoftStrategy extends PassportStrategy(Strategy) {
}

/**
* * Check to see if entry for Microsoft is present in the Account table for user
* * If user was created with another provider findUserByEmail may return true
* Check to see if entry for Microsoft is present in the Account table for user
* If user was created with another provider findUserByEmail may return true
*/
const providerAccountExists =
await this.authService.checkIfProviderAccountExists(user.value, profile);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ export class RTJwtStrategy extends PassportStrategy(Strategy, 'jwt-refresh') {
super({
jwtFromRequest: ExtractJwt.fromExtractors([
(request: Request) => {
const RTCookie = request.cookies['refresh_token'];
const RTCookie = request.cookies?.['refresh_token'];
if (!RTCookie) {
console.error('`refresh_token` not found');
throw new ForbiddenException(COOKIES_NOT_FOUND);
}
return RTCookie;
},
]),
secretOrKey: configService.get('JWT_SECRET'),
secretOrKey: configService.get<string>('JWT_SECRET'),
});
}

Expand Down
7 changes: 7 additions & 0 deletions packages/hoppscotch-backend/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ export const AUTH_PROVIDER_NOT_SPECIFIED = 'auth/provider_not_specified';
export const AUTH_PROVIDER_NOT_CONFIGURED =
'auth/provider_not_configured_correctly';

/**
* Email not provided by OAuth provider
* (SSO Strategies)
*/
export const AUTH_EMAIL_NOT_PROVIDED_BY_OAUTH =
'auth/email_not_provided_by_oauth';

/**
* Environment variable "VITE_ALLOWED_AUTH_PROVIDERS" is not present in .env file
*/
Expand Down
3 changes: 1 addition & 2 deletions packages/hoppscotch-common/assets/scss/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ a {
@apply shadow-none #{!important};
@apply fixed;
@apply inline-flex;
@apply -mt-7;
}
}

Expand All @@ -169,7 +168,7 @@ a {
@apply shadow;

.tippy-content {
@apply flex;
@apply flex flex-col;
@apply text-tiny text-primary;
@apply font-semibold;
@apply px-2 py-1;
Expand Down
Loading
Loading