Skip to content

Commit

Permalink
Merge pull request #4064 from novuhq/nv-2781-move-auth-logic-to-appli…
Browse files Browse the repository at this point in the history
…cation-generic

Nv 2781 move auth logic to application generic
  • Loading branch information
djabarovgeorge authored Sep 3, 2023
2 parents e3c11e3 + 9e59391 commit 0b382d0
Show file tree
Hide file tree
Showing 38 changed files with 473 additions and 179 deletions.
58 changes: 9 additions & 49 deletions apps/api/src/app/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,13 @@ import { MemberRepository, OrganizationRepository, UserRepository, MemberEntity
import { JwtService } from '@nestjs/jwt';
import { AuthGuard } from '@nestjs/passport';
import { IJwtPayload } from '@novu/shared';
import { AuthService } from './services/auth.service';
import { UserRegistrationBodyDto } from './dtos/user-registration.dto';
import { UserRegister } from './usecases/register/user-register.usecase';
import { UserRegisterCommand } from './usecases/register/user-register.command';
import { Login } from './usecases/login/login.usecase';
import { LoginBodyDto } from './dtos/login.dto';
import { LoginCommand } from './usecases/login/login.command';
import { UserSession } from '../shared/framework/user.decorator';
import { SwitchEnvironment } from './usecases/switch-environment/switch-environment.usecase';
import { SwitchEnvironmentCommand } from './usecases/switch-environment/switch-environment.command';
import { SwitchOrganization } from './usecases/switch-organization/switch-organization.usecase';
import { SwitchOrganizationCommand } from './usecases/switch-organization/switch-organization.command';
import { JwtAuthGuard } from './framework/auth.guard';
import { PasswordResetRequestCommand } from './usecases/password-reset-request/password-reset-request.command';
import { PasswordResetRequest } from './usecases/password-reset-request/password-reset-request.usecase';
Expand All @@ -40,6 +35,14 @@ import { PasswordReset } from './usecases/password-reset/password-reset.usecase'
import { ApiException } from '../shared/exceptions/api.exception';
import { ApiExcludeController, ApiTags } from '@nestjs/swagger';
import { PasswordResetBodyDto } from './dtos/password-reset.dto';
import {
AuthService,
buildOauthRedirectUrl,
SwitchEnvironment,
SwitchEnvironmentCommand,
SwitchOrganization,
SwitchOrganizationCommand,
} from '@novu/application-generic';

@Controller('/auth')
@UseInterceptors(ClassSerializerInterceptor)
Expand Down Expand Up @@ -80,50 +83,7 @@ export class AuthController {
@Get('/github/callback')
@UseGuards(AuthGuard('github'))
async githubCallback(@Req() request, @Res() response) {
if (!request.user || !request.user.token) {
return response.redirect(`${process.env.FRONT_BASE_URL + '/auth/login'}?error=AuthenticationError`);
}

let url = process.env.FRONT_BASE_URL + '/auth/login';
const redirectUrl = JSON.parse(request.query.state).redirectUrl;

/**
* Make sure we only allow localhost redirects for CLI use and our own success route
* https://github.com/novuhq/novu/security/code-scanning/3
*/
if (redirectUrl && redirectUrl.startsWith('http://localhost:') && !redirectUrl.includes('@')) {
url = redirectUrl;
}

url += `?token=${request.user.token}`;

if (request.user.newUser) {
url += '&newUser=true';
}

/**
* partnerCode, next and configurationId are required during external partners integration
* such as vercel integration etc
*/
const partnerCode = JSON.parse(request.query.state).partnerCode;
if (partnerCode) {
url += `&code=${partnerCode}`;
}

const next = JSON.parse(request.query.state).next;
if (next) {
url += `&next=${next}`;
}

const configurationId = JSON.parse(request.query.state).configurationId;
if (configurationId) {
url += `&configurationId=${configurationId}`;
}

const invitationToken = JSON.parse(request.query.state).invitationToken;
if (invitationToken) {
url += `&invitationToken=${invitationToken}`;
}
const url = buildOauthRedirectUrl(request);

return response.redirect(url);
}
Expand Down
9 changes: 6 additions & 3 deletions apps/api/src/app/auth/auth.module.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { MiddlewareConsumer, Module, NestModule, Provider, RequestMethod } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule, PassportStrategy } from '@nestjs/passport';
import { PassportModule } from '@nestjs/passport';
import * as passport from 'passport';

import { AuthProviderEnum } from '@novu/shared';
import { AuthService } from '@novu/application-generic';

import { RolesGuard } from './framework/roles.guard';
import { JwtStrategy } from './services/passport/jwt.strategy';
import { AuthController } from './auth.controller';
import { UserModule } from '../user/user.module';
import { AuthService } from './services/auth.service';
import { USE_CASES } from './usecases';
import { SharedModule } from '../shared/shared.module';
import { GitHubStrategy } from './services/passport/github.strategy';
Expand Down Expand Up @@ -56,7 +59,7 @@ export class AuthModule implements NestModule {
if (process.env.GITHUB_OAUTH_CLIENT_ID) {
consumer
.apply(
passport.authenticate('github', {
passport.authenticate(AuthProviderEnum.GITHUB, {
session: false,
scope: ['user:email'],
})
Expand Down
3 changes: 1 addition & 2 deletions apps/api/src/app/auth/framework/auth.guard.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { ExecutionContext, forwardRef, Inject, Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Reflector } from '@nestjs/core';
import { AuthService } from '../services/auth.service';
import { Instrument, PinoLogger } from '@novu/application-generic';
import { AuthService, Instrument, PinoLogger } from '@novu/application-generic';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { CanActivate, ExecutionContext, forwardRef, Inject, Injectable, Unauthor
import { Reflector } from '@nestjs/core';
import { IJwtPayload } from '@novu/shared';
import * as jwt from 'jsonwebtoken';
import { AuthService } from '../services/auth.service';
import { AuthService } from '@novu/application-generic';

@Injectable()
export class RootEnvironmentGuard implements CanActivate {
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/app/auth/services/passport/github.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { PassportStrategy } from '@nestjs/passport';
import * as githubPassport from 'passport-github2';
import { Metadata, StateStoreStoreCallback, StateStoreVerifyCallback } from 'passport-oauth2';
import { AuthProviderEnum } from '@novu/shared';
import { AuthService } from '../auth.service';
import { AuthService } from '@novu/application-generic';

@Injectable()
export class GitHubStrategy extends PassportStrategy(githubPassport.Strategy, 'github') {
export class GitHubStrategy extends PassportStrategy(githubPassport.Strategy, AuthProviderEnum.GITHUB) {
constructor(private authService: AuthService) {
super({
clientID: process.env.GITHUB_OAUTH_CLIENT_ID,
Expand Down
3 changes: 1 addition & 2 deletions apps/api/src/app/auth/services/passport/jwt.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { IJwtPayload } from '@novu/shared';
import { AuthService } from '../auth.service';
import { Instrument, PinoLogger } from '@novu/application-generic';
import { AuthService, Instrument, PinoLogger } from '@novu/application-generic';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { ISubscriberJwt } from '@novu/shared';
import { AuthService } from '../auth.service';
import { AuthService } from '@novu/application-generic';

@Injectable()
export class JwtSubscriberStrategy extends PassportStrategy(Strategy, 'subscriberJwt') {
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/app/auth/usecases/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { SwitchEnvironment, SwitchOrganization } from '@novu/application-generic';

import { PasswordResetRequest } from './password-reset-request/password-reset-request.usecase';
import { UserRegister } from './register/user-register.usecase';
import { Login } from './login/login.usecase';
import { SwitchEnvironment } from './switch-environment/switch-environment.usecase';
import { SwitchOrganization } from './switch-organization/switch-organization.usecase';
import { PasswordReset } from './password-reset/password-reset.usecase';

export const USE_CASES = [
Expand Down
3 changes: 1 addition & 2 deletions apps/api/src/app/auth/usecases/login/login.usecase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ import * as bcrypt from 'bcrypt';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { differenceInMinutes, parseISO } from 'date-fns';
import { UserRepository, UserEntity, OrganizationRepository } from '@novu/dal';
import { AnalyticsService } from '@novu/application-generic';
import { AnalyticsService, AuthService } from '@novu/application-generic';

import { LoginCommand } from './login.command';
import { ApiException } from '../../../shared/exceptions/api.exception';
import { normalizeEmail } from '../../../shared/helpers/email-normalization.service';
import { AuthService } from '../../services/auth.service';
import { createHash } from '../../../shared/helpers/hmac.service';

@Injectable()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ import { Injectable } from '@nestjs/common';
import * as bcrypt from 'bcrypt';
import { isBefore, subDays } from 'date-fns';
import { UserRepository } from '@novu/dal';
import { buildUserKey, InvalidateCacheService } from '@novu/application-generic';
import { AuthService, buildUserKey, InvalidateCacheService } from '@novu/application-generic';

import { PasswordResetCommand } from './password-reset.command';
import { ApiException } from '../../../shared/exceptions/api.exception';
import { AuthService } from '../../services/auth.service';

@Injectable()
export class PasswordReset {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import { Inject, Injectable } from '@nestjs/common';
import { OrganizationEntity, UserRepository } from '@novu/dal';
import * as bcrypt from 'bcrypt';
import { SignUpOriginEnum } from '@novu/shared';
import { AnalyticsService } from '@novu/application-generic';
import { AnalyticsService, AuthService } from '@novu/application-generic';

import { AuthService } from '../../services/auth.service';
import { UserRegisterCommand } from './user-register.command';
import { normalizeEmail } from '../../../shared/helpers/email-normalization.service';
import { ApiException } from '../../../shared/exceptions/api.exception';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Injectable, Logger, NotFoundException, Scope } from '@nestjs/common';

import { MemberEntity, OrganizationRepository, UserEntity, MemberRepository, UserRepository } from '@novu/dal';
import { MemberStatusEnum } from '@novu/shared';
import { Novu } from '@novu/node';
import { AuthService } from '@novu/application-generic';

import { ApiException } from '../../../shared/exceptions/api.exception';
import { AcceptInviteCommand } from './accept-invite.command';
import { AuthService } from '../../../auth/services/auth.service';
import { capitalize } from '../../../shared/services/helper/helper.service';

@Injectable({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Injectable } from '@nestjs/common';
import { faker } from '@faker-js/faker';

import { AuthService } from '@novu/application-generic';

import { SeedDataCommand } from './seed-data.command';
import { AuthService } from '../../../auth/services/auth.service';
import { UserRegister } from '../../../auth/usecases/register/user-register.usecase';
import { UserRegisterCommand } from '../../../auth/usecases/register/user-register.command';
import { ApiException } from '../../../shared/exceptions/api.exception';
Expand Down
3 changes: 2 additions & 1 deletion apps/api/src/app/user/usecases/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CreateUser } from './create-user/create-user.usecase';
import { CreateUser } from '@novu/application-generic';

import { GetMyProfileUsecase } from './get-my-profile/get-my-profile.usecase';
import { UpdateOnBoardingUsecase } from './update-on-boarding/update-on-boarding.usecase';
import { UpdateOnBoardingTourUsecase } from './update-on-boarding-tour/update-on-boarding-tour.usecase';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import {
CreateSubscriberCommand,
SelectIntegrationCommand,
SelectIntegration,
AuthService,
} from '@novu/application-generic';

import { AuthService } from '../../../auth/services/auth.service';
import { ApiException } from '../../../shared/exceptions/api.exception';
import { InitializeSessionCommand } from './initialize-session.command';

Expand Down
1 change: 1 addition & 0 deletions apps/web/src/design-system/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,4 @@ export { DisconnectGradient } from './gradient/DisconnectGradient';
export { BoltOutlinedGradient } from './gradient/BoltOutlinedGradient';

export { GitHub } from './social/GitHub';
export { Google } from './social/Google';
24 changes: 24 additions & 0 deletions apps/web/src/design-system/icons/social/Google.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
/* eslint-disable */
export function Google(props: React.ComponentPropsWithoutRef<'svg'>) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="20" viewBox="0 0 21 20" fill="none" {...props}>
<path
d="M11.2083 8.18184V12.0546H16.5901C16.3538 13.3001 15.6446 14.3546 14.581 15.0637L17.8265 17.5819C19.7174 15.8365 20.8083 13.2729 20.8083 10.2274C20.8083 9.51832 20.7447 8.83643 20.6265 8.18195L11.2083 8.18184Z"
fill="#4285F4"
/>
<path
d="M5.60401 11.9034L4.87203 12.4637L2.28107 14.4819C3.92653 17.7455 7.29902 20.0001 11.2081 20.0001C13.9081 20.0001 16.1719 19.1092 17.8265 17.5819L14.581 15.0637C13.6901 15.6637 12.5535 16.0274 11.2081 16.0274C8.60811 16.0274 6.39907 14.2729 5.6081 11.9092L5.60401 11.9034Z"
fill="#34A853"
/>
<path
d="M2.28108 5.51822C1.59929 6.86362 1.20842 8.38183 1.20842 10C1.20842 11.6182 1.59928 13.1365 2.28107 14.4819C2.28107 14.4909 5.60843 11.8999 5.60843 11.8999C5.40843 11.2999 5.29021 10.6636 5.29021 9.99989C5.29021 9.33617 5.40843 8.69984 5.60843 8.09984L2.28108 5.51822Z"
fill="#FBBC05"
/>
<path
d="M11.2083 3.98184C12.6811 3.98184 13.9901 4.49092 15.0356 5.47276L17.8992 2.60914C16.1628 0.990977 13.9084 0 11.2083 0C7.29923 0 3.92654 2.24546 2.28108 5.51822L5.60843 8.09984C6.39929 5.73616 8.60831 3.98184 11.2083 3.98184Z"
fill="#EA4335"
/>
</svg>
);
}
56 changes: 39 additions & 17 deletions apps/web/src/pages/auth/components/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import { Divider, Button as MantineButton, Center } from '@mantine/core';
import { useAuthContext } from '../../../components/providers/AuthProvider';
import { api } from '../../../api/api.client';
import { PasswordInput, Button, colors, Input, Text } from '../../../design-system';
import { GitHub } from '../../../design-system/icons';
import { GitHub, Google } from '../../../design-system/icons';
import { IS_DOCKER_HOSTED } from '../../../config';
import { useVercelParams } from '../../../hooks';
import { useAcceptInvite } from './useAcceptInvite';
import { buildGithubLink, buildVercelGithubLink } from './gitHubUtils';
import { buildGithubLink, buildGoogleLink, buildVercelGithubLink } from './gitHubUtils';
import { ROUTES } from '../../../constants/routes.enum';
import { When } from '../../../components/utils/When';

type LoginFormProps = {
invitationToken?: string;
Expand All @@ -41,6 +42,7 @@ export function LoginForm({ email, invitationToken }: LoginFormProps) {
const githubLink = isFromVercel
? buildVercelGithubLink({ code, next, configurationId })
: buildGithubLink({ invitationToken });
const googleLink = buildGoogleLink({ invitationToken });

const {
register,
Expand Down Expand Up @@ -95,24 +97,39 @@ export function LoginForm({ email, invitationToken }: LoginFormProps) {

return (
<>
{!IS_DOCKER_HOSTED && (
<When truthy={!IS_DOCKER_HOSTED}>
<>
<GitHubButton
component="a"
href={githubLink}
my={30}
variant="white"
fullWidth
radius="md"
leftIcon={<GitHub />}
sx={{ color: colors.B40, fontSize: '16px', fontWeight: 700, height: '50px' }}
data-test-id="github-button"
>
Sign In with GitHub
</GitHubButton>
<OAuth>
<GitHubButton
component="a"
href={githubLink}
my={30}
variant="white"
fullWidth
radius="md"
leftIcon={<GitHub />}
sx={{ color: colors.B40, fontSize: '16px', fontWeight: 700, height: '50px', marginRight: 10 }}
data-test-id="github-button"
>
Sign In with GitHub
</GitHubButton>
<GitHubButton
component="a"
href={googleLink}
my={30}
variant="white"
fullWidth
radius="md"
leftIcon={<Google />}
data-test-id="google-button"
sx={{ color: colors.B40, fontSize: '16px', fontWeight: 700, height: '50px', marginLeft: 10 }}
>
Sign In with Google
</GitHubButton>
</OAuth>
<Divider label={<Text color={colors.B40}>Or</Text>} color={colors.B30} labelPosition="center" my="md" />
</>
)}
</When>
<form noValidate onSubmit={handleSubmit(onLogin)}>
<Input
error={errors.email?.message || emailServerError}
Expand Down Expand Up @@ -174,6 +191,11 @@ export function LoginForm({ email, invitationToken }: LoginFormProps) {
);
}

const OAuth = styled.div`
display: flex;
justify-content: space-between;
`;

const GitHubButton = styled(MantineButton)<{
component: 'a';
my: number;
Expand Down
10 changes: 10 additions & 0 deletions apps/web/src/pages/auth/components/gitHubUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ export const buildGithubLink = ({ invitationToken }: { invitationToken?: string
return `${API_ROOT}/v1/auth/github?${queryParams.toString()}`;
};

export const buildGoogleLink = ({ invitationToken }: { invitationToken?: string }) => {
const queryParams = new URLSearchParams();
queryParams.append('source', SignUpOriginEnum.WEB);
if (invitationToken) {
queryParams.append('invitationToken', invitationToken);
}

return `${API_ROOT}/v1/auth/google?${queryParams.toString()}`;
};

export const buildVercelGithubLink = ({
code,
next,
Expand Down
Loading

0 comments on commit 0b382d0

Please sign in to comment.