Skip to content

Commit

Permalink
Merge branch 'develop' into feat-api/fetch-invitations-workspace
Browse files Browse the repository at this point in the history
  • Loading branch information
rajdip-b authored Dec 10, 2024
2 parents 8cc2215 + a33f4b5 commit a013dfb
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 47 deletions.
18 changes: 18 additions & 0 deletions api-collection/Auth Controller/Logout.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
meta {
name: Logout
type: http
seq: 6
}

post {
url: {{BASE_URL}}/api/auth/logout
body: none
auth: none
}


docs {
## Description

This endpoint clears the token cookie, ensuring the user is logged out securely.
}
6 changes: 6 additions & 0 deletions apps/api/src/auth/controller/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,4 +207,10 @@ export class AuthController {
)
}
}

@Post('logout')
async logout(@Res() res: Response): Promise<void> {
await this.authService.logout(res)
res.status(HttpStatus.OK).send({ message: 'Logged out successfully' })
}
}
12 changes: 12 additions & 0 deletions apps/api/src/auth/service/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { CacheService } from '@/cache/cache.service'
import { generateOtp } from '@/common/util'
import { createUser, getUserByEmailOrId } from '@/common/user'
import { UserWithWorkspace } from '@/user/user.types'
import { Response } from 'express'

@Injectable()
export class AuthService {
Expand Down Expand Up @@ -219,4 +220,15 @@ export class AuthService {
private async generateToken(id: string) {
return await this.jwt.signAsync({ id })
}

/**
* Clears the token cookie on logout
* @param res The response object
*/
async logout(res: Response): Promise<void> {
res.clearCookie('token', {
domain: process.env.DOMAIN ?? 'localhost'
})
this.logger.log('User logged out and token cookie cleared.')
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { IsOptional, IsString } from 'class-validator'
import { IsNotEmpty, IsOptional, IsString, Matches } from 'class-validator'

export class CreateEnvironment {
@IsString()
@IsNotEmpty()
@Matches(/^[a-zA-Z0-9-_]{1,64}$/)
name: string

@IsString()
Expand Down
17 changes: 17 additions & 0 deletions apps/api/src/environment/environment.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,23 @@ describe('Environment Controller Tests', () => {
expect(environmentFromDb).toBeDefined()
})

it('should not be able to create an environment with an empty name', async () => {
const response = await app.inject({
method: 'POST',
url: `/environment/${project1.slug}`,
payload: {
name: '',
description: 'Empty name test'
},
headers: {
'x-e2e-user-email': user1.email
}
})

expect(response.statusCode).toBe(400)
expect(response.json().message).toContain('name should not be empty')
})

it('should not be able to create an environment in a project that does not exist', async () => {
const response = await app.inject({
method: 'POST',
Expand Down
34 changes: 34 additions & 0 deletions apps/api/src/mail/emails/otp-email-template.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as React from 'react'
import { Text, Section } from '@react-email/components'
import { otpStyle, text, workspaceDetails } from './styles/common-styles'
import BaseEmailTemplate from './components/base-email-template'

interface OTPEmailTemplateProps {
otp: string
}

export const OTPEmailTemplate = ({ otp }: OTPEmailTemplateProps) => {
return (
<BaseEmailTemplate
previewText="Your One Time Password (OTP) for Keyshade"
heading="Your One Time Password (OTP)"
>
<Text style={text}>Dear User,</Text>
<Text style={text}>
We’ve sent you this email to verify your Keyshade account. Your One-Time
Password (OTP) is:
</Text>
<Section style={workspaceDetails}>
<Text style={otpStyle}>
<strong>{otp}</strong>
</Text>
</Section>
<Text style={text}>
This OTP will expire in <strong>5 minutes</strong>. Please use it to
complete your action on Keyshade.
</Text>
</BaseEmailTemplate>
)
}

export default OTPEmailTemplate
12 changes: 10 additions & 2 deletions apps/api/src/mail/emails/styles/common-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@ export const text: CSSProperties = {

export const workspaceDetails: CSSProperties = {
width: '100%',
backgroundColor: '#fafafa',
borderRadius: '5px',
margin: '20px 0px',
padding: '10px 20px'
padding: '10px 20px',
border: '1px solid #eee',
backgroundColor: '#fafafa'
}

export const workspaceInfo: CSSProperties = {
Expand All @@ -60,6 +61,13 @@ export const ctaButton: CSSProperties = {
borderRadius: '5px'
}

export const otpStyle: CSSProperties = {
...workspaceInfo,
fontSize: '26px',
textAlign: 'center',
letterSpacing: '8px'
}

export const footer: CSSProperties = {
borderTop: '1px solid #eaeaea',
padding: '20px'
Expand Down
55 changes: 17 additions & 38 deletions apps/api/src/mail/services/mail.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Transporter, createTransport } from 'nodemailer'
import RemovedFromWorkspaceEmail from '../emails/workspace-removal'
import { render } from '@react-email/render'
import WorkspaceInvitationEmail from '../emails/workspace-invitation'
import OTPEmailTemplate from '../emails/otp-email-template'

@Injectable()
export class MailService implements IMailService {
Expand Down Expand Up @@ -50,47 +51,25 @@ export class MailService implements IMailService {
}

async sendOtp(email: string, otp: string): Promise<void> {
const subject = 'Your Login OTP'
const body = `<!DOCTYPE html>
<html>
<head>
<title>OTP Verification</title>
</head>
<body>
<h1>Welcome to keyshade!</h1>
<p>Hello there!</p>
<p>We have sent you this email to verify your account.</p>
<p>Your One Time Password (OTP) is: <strong>${otp}</strong></p>
<p>This OTP will expire in <strong>5 minutes</strong>.</p>
<p>Please enter this OTP in the application to verify your account.</p>
<p>Thank you for choosing us.</p>
<p>Best Regards,</p>
<p>keyshade Team</p>
</body>
</html>
`
const subject = 'Your One Time Password (OTP) for Keyshade'

const body = await render(
OTPEmailTemplate({
otp
})
)

await this.sendEmail(email, subject, body)
}
async sendEmailChangedOtp(email: string, otp: string): Promise<void> {
const subject = 'Your OTP for Email Change'
const body = `<!DOCTYPE html>
<html>
<head>
<title>OTP Verification</title>
</head>
<body>
<h1>Are you trying to change your email?</h1>
<p>Hello there!</p>
<p>We have sent you this email to verify your new email.</p>
<p>Your One Time Password (OTP) is: <strong>${otp}</strong></p>
<p>This OTP will expire in <strong>5 minutes</strong>.</p>
<p>Please enter this OTP in the application to verify your new email.</p>
<p>Thank you.</p>
<p>Best Regards,</p>
<p>keyshade Team</p>
</body>
</html>
`
const subject = 'Your Keyshade Email Change One Time Password (OTP)'

const body = await render(
OTPEmailTemplate({
otp
})
)

await this.sendEmail(email, subject, body)
}
async accountLoginEmail(email: string): Promise<void> {
Expand Down
8 changes: 2 additions & 6 deletions apps/platform/src/components/shared/navbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useEffect, useState } from 'react'
import { usePathname } from 'next/navigation'
import Link from 'next/link'
import { DropdownSVG } from '@public/svg/shared'
import type { User } from '@keyshade/schema'
import SearchModel from './searchModel'
import {
DropdownMenu,
Expand All @@ -16,7 +17,6 @@ import {
} from '@/components/ui/dropdown-menu'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import LineTab from '@/components/ui/line-tab'
import { zUser, type User } from '@/types'

interface UserNameImage {
name: string | null
Expand All @@ -32,11 +32,7 @@ async function fetchNameImage(): Promise<UserNameImage | undefined> {
credentials: 'include'
}
)
const userData: User = (await response.json()) as User
const { success, data } = zUser.safeParse(userData)
if (!success) {
throw new Error('Invalid data')
}
const data: User = (await response.json()) as User
return {
name: data.name?.split(' ')[0] ?? data.email.split('@')[0],
image: data.profilePictureUrl
Expand Down

0 comments on commit a013dfb

Please sign in to comment.