feat: activate your email
This commit is contained in:
parent
498d4ef3c4
commit
e534476189
|
|
@ -30,6 +30,12 @@ export class AuthController {
|
|||
getOrgFromCookie
|
||||
);
|
||||
|
||||
if (body.provider === 'LOCAL') {
|
||||
response.header('activate', 'true');
|
||||
response.status(200).json({ activate: true });
|
||||
return;
|
||||
}
|
||||
|
||||
response.cookie('auth', jwt, {
|
||||
domain:
|
||||
'.' + new URL(removeSubdomain(process.env.FRONTEND_URL!)).hostname,
|
||||
|
|
@ -132,6 +138,29 @@ export class AuthController {
|
|||
return this._authService.oauthLink(provider);
|
||||
}
|
||||
|
||||
@Post('/activate')
|
||||
async activate(
|
||||
@Body('code') code: string,
|
||||
@Res({ passthrough: true }) response: Response
|
||||
) {
|
||||
const activate = await this._authService.activate(code);
|
||||
if (!activate) {
|
||||
return response.status(200).send({ can: false });
|
||||
}
|
||||
|
||||
response.cookie('auth', activate, {
|
||||
domain:
|
||||
'.' + new URL(removeSubdomain(process.env.FRONTEND_URL!)).hostname,
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365),
|
||||
});
|
||||
|
||||
response.header('onboarding', 'true');
|
||||
return response.status(200).send({ can: true });
|
||||
}
|
||||
|
||||
@Post('/oauth/:provider/exists')
|
||||
async oauthExists(
|
||||
@Body('code') code: string,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ async function bootstrap() {
|
|||
rawBody: true,
|
||||
cors: {
|
||||
credentials: true,
|
||||
exposedHeaders: ['reload', 'onboarding'],
|
||||
exposedHeaders: ['reload', 'onboarding', 'activate'],
|
||||
origin: [process.env.FRONTEND_URL],
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -36,8 +36,11 @@ export class AuthMiddleware implements NestMiddleware {
|
|||
const orgHeader = req.cookies.showorg || req.headers.showorg;
|
||||
|
||||
if (!user) {
|
||||
removeAuth(res);
|
||||
res.status(401).send('Unauthorized');
|
||||
throw new HttpForbiddenException();
|
||||
}
|
||||
|
||||
if (!user.activated) {
|
||||
throw new HttpForbiddenException();
|
||||
}
|
||||
|
||||
if (user?.isSuperAdmin && req.cookies.impersonate) {
|
||||
|
|
|
|||
|
|
@ -10,13 +10,15 @@ import dayjs from 'dayjs';
|
|||
import { NewsletterService } from '@gitroom/nestjs-libraries/services/newsletter.service';
|
||||
import { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';
|
||||
import { ForgotReturnPasswordDto } from '@gitroom/nestjs-libraries/dtos/auth/forgot-return.password.dto';
|
||||
import { EmailService } from '@gitroom/nestjs-libraries/services/email.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
private _userService: UsersService,
|
||||
private _organizationService: OrganizationService,
|
||||
private _notificationService: NotificationService
|
||||
private _notificationService: NotificationService,
|
||||
private _emailService: EmailService,
|
||||
) {}
|
||||
async routeAuth(
|
||||
provider: Provider,
|
||||
|
|
@ -31,7 +33,7 @@ export class AuthService {
|
|||
}
|
||||
|
||||
const create = await this._organizationService.createOrgAndUser(body);
|
||||
NewsletterService.register(body.email);
|
||||
|
||||
const addedOrg =
|
||||
addToOrg && typeof addToOrg !== 'boolean'
|
||||
? await this._organizationService.addUserToOrg(
|
||||
|
|
@ -41,14 +43,21 @@ export class AuthService {
|
|||
addToOrg.role
|
||||
)
|
||||
: false;
|
||||
return { addedOrg, jwt: await this.jwt(create.users[0].user) };
|
||||
|
||||
const obj = { addedOrg, jwt: await this.jwt(create.users[0].user) };
|
||||
await this._emailService.sendEmail(body.email, 'Activate your account', `Click <a href="${process.env.FRONTEND_URL}/auth/activate/${obj.jwt}">here</a> to activate your account`);
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (!user || !AuthChecker.comparePassword(body.password, user.password)) {
|
||||
throw new Error('Invalid user');
|
||||
throw new Error('Invalid user name or password');
|
||||
}
|
||||
|
||||
return { jwt: await this.jwt(user) };
|
||||
if (!user.activated) {
|
||||
throw new Error('User is not activated');
|
||||
}
|
||||
|
||||
return { addedOrg: false, jwt: await this.jwt(user) };
|
||||
}
|
||||
|
||||
const user = await this.loginOrRegisterProvider(
|
||||
|
|
@ -152,6 +161,22 @@ export class AuthService {
|
|||
return this._userService.updatePassword(user.id, body.password);
|
||||
}
|
||||
|
||||
async activate(code: string) {
|
||||
const user = AuthChecker.verifyJWT(code) as { id: string, activated: boolean, email: string };
|
||||
if (user.id && !user.activated) {
|
||||
const getUserAgain = await this._userService.getUserByEmail(user.email);
|
||||
if (getUserAgain.activated) {
|
||||
return false;
|
||||
}
|
||||
await this._userService.activateUser(user.id);
|
||||
user.activated = true;
|
||||
await NewsletterService.register(user.email);
|
||||
return this.jwt(user as any);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
oauthLink(provider: string) {
|
||||
const providerInstance = ProvidersFactory.loadProvider(
|
||||
provider as Provider
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
import { isGeneral } from '@gitroom/react/helpers/is.general';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
import { Metadata } from 'next';
|
||||
import { AfterActivate } from '@gitroom/frontend/components/auth/after.activate';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: `${isGeneral() ? 'Postiz' : 'Gitroom'} - Activate your account`,
|
||||
description: '',
|
||||
};
|
||||
|
||||
export default async function Auth() {
|
||||
return <AfterActivate />;
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import { isGeneral } from '@gitroom/react/helpers/is.general';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
import {Metadata} from "next";
|
||||
import { Activate } from '@gitroom/frontend/components/auth/activate';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: `${isGeneral() ? 'Postiz' : 'Gitroom'} - Activate your account`,
|
||||
description: '',
|
||||
};
|
||||
|
||||
export default async function Auth() {
|
||||
return (
|
||||
<Activate />
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
'use client';
|
||||
|
||||
export function Activate() {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-left mb-4 cursor-pointer">
|
||||
Activate your account
|
||||
</h1>
|
||||
</div>
|
||||
<div className="text-white">
|
||||
Thank you for registering!<br />Please check your email to activate your account.
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
'use client';
|
||||
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { LoadingComponent } from '@gitroom/frontend/components/layout/loading';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useParams } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
|
||||
export const AfterActivate = () => {
|
||||
const fetch = useFetch();
|
||||
const params = useParams();
|
||||
const [showLoader, setShowLoader] = useState(true);
|
||||
const run = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!run.current) {
|
||||
run.current = true;
|
||||
loadCode();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loadCode = useCallback(async () => {
|
||||
if (params.code) {
|
||||
const { can } = await (
|
||||
await fetch(`/auth/activate`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ code: params.code }),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
).json();
|
||||
|
||||
if (!can) {
|
||||
setShowLoader(false);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>{showLoader ? <LoadingComponent /> : (<>This user is already activated,<br /><Link href="/auth/login" className="underline">Click here to go back to login</Link></>)}</>
|
||||
);
|
||||
};
|
||||
|
|
@ -44,7 +44,7 @@ export function Login() {
|
|||
|
||||
if (login.status === 400) {
|
||||
form.setError('email', {
|
||||
message: 'Invalid email or password',
|
||||
message: await login.text(),
|
||||
});
|
||||
|
||||
setLoading(false);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
|||
import { classValidatorResolver } from '@hookform/resolvers/class-validator';
|
||||
import { CreateOrgUserDto } from '@gitroom/nestjs-libraries/dtos/auth/create.org.user.dto';
|
||||
import { GithubProvider } from '@gitroom/frontend/components/auth/providers/github.provider';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { LoadingComponent } from '@gitroom/frontend/components/layout/loading';
|
||||
import interClass from '@gitroom/react/helpers/inter.font';
|
||||
import { isGeneral } from '@gitroom/react/helpers/is.general';
|
||||
|
|
@ -73,6 +73,7 @@ export function RegisterAfter({
|
|||
}) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const getQuery = useSearchParams();
|
||||
const router = useRouter();
|
||||
|
||||
const isAfterProvider = useMemo(() => {
|
||||
return !!token && !!provider;
|
||||
|
|
@ -105,6 +106,12 @@ export function RegisterAfter({
|
|||
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
console.log(register.headers.get('activate'), register.headers.get('Activate'));
|
||||
|
||||
if (register.headers.get('activate')) {
|
||||
router.push('/auth/activate');
|
||||
}
|
||||
};
|
||||
|
||||
const rootDomain = useMemo(() => {
|
||||
|
|
|
|||
|
|
@ -186,6 +186,7 @@ export class OrganizationRepository {
|
|||
role: Role.SUPERADMIN,
|
||||
user: {
|
||||
create: {
|
||||
activated: body.provider !== 'LOCAL',
|
||||
email: body.email,
|
||||
password: body.password
|
||||
? AuthService.hashPassword(body.password)
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ model User {
|
|||
updatedAt DateTime @updatedAt
|
||||
lastReadNotifications DateTime @default(now())
|
||||
inviteId String?
|
||||
activated Boolean @default(true)
|
||||
items ItemUser[]
|
||||
marketplace Boolean @default(true)
|
||||
account String?
|
||||
|
|
|
|||
|
|
@ -64,6 +64,17 @@ export class UsersRepository {
|
|||
});
|
||||
}
|
||||
|
||||
activateUser(id: string) {
|
||||
return this._user.model.user.update({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
data: {
|
||||
activated: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getUserByProvider(providerId: string, provider: Provider) {
|
||||
return this._user.model.user.findFirst({
|
||||
where: {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ export class UsersService {
|
|||
return this._usersRepository.getUserByProvider(providerId, provider);
|
||||
}
|
||||
|
||||
activateUser(id: string) {
|
||||
return this._usersRepository.activateUser(id);
|
||||
}
|
||||
|
||||
updatePassword(id: string, password: string) {
|
||||
return this._usersRepository.updatePassword(id, password);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ export class EmailService {
|
|||
console.log('No Resend API Key found, skipping email sending');
|
||||
return;
|
||||
}
|
||||
await resend.emails.send({
|
||||
from: process.env.IS_GENERAL === 'true' ? 'Nevo <nevo@postiz.com>' : 'Nevo <nevo@gitroom.com>',
|
||||
const sends = await resend.emails.send({
|
||||
from: process.env.IS_GENERAL === 'true' ? 'Nevo <nevo@gitroom.com>' : 'Nevo <nevo@gitroom.com>',
|
||||
to,
|
||||
subject,
|
||||
html,
|
||||
|
|
|
|||
Loading…
Reference in New Issue