Skip to content

Commit ebc7522

Browse files
committed
combined all iam services
1 parent a2a3f3f commit ebc7522

File tree

4 files changed

+58
-155
lines changed

4 files changed

+58
-155
lines changed

src/lib/server/api/controllers/iam.controller.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,43 +8,34 @@ import { signInEmailDto } from '../../../dtos/signin-email.dto';
88
import { updateEmailDto } from '../../../dtos/update-email.dto';
99
import { verifyEmailDto } from '../../../dtos/verify-email.dto';
1010
import { registerEmailDto } from '../../../dtos/register-email.dto';
11-
import { EmailVerificationsService } from '../services/email-verifications.service';
12-
import { LoginRequestsService } from '../services/login-requests.service';
1311
import type { HonoTypes } from '../common/types/hono.type';
1412
import type { Controller } from '../common/inferfaces/controller.interface';
1513
import { limiter } from '../middlewares/rate-limiter.middlware';
1614
import { requireAuth } from '../middlewares/auth.middleware';
17-
import TestJob from '../jobs/test.job';
1815

1916
@injectable()
2017
export class IamController implements Controller {
2118
private controller = new Hono<HonoTypes>();
2219

2320
constructor(
2421
@inject(IamService) private iamService: IamService,
25-
@inject(LoginRequestsService) private loginRequestsService: LoginRequestsService,
26-
@inject(EmailVerificationsService) private emailVerificationsService: EmailVerificationsService,
2722
@inject(LuciaProvider) private lucia: LuciaProvider,
28-
@inject(TestJob) private testJob: TestJob
2923
) { }
3024

3125
routes() {
3226
return this.controller
3327
.get('/user', async (c) => {
3428
const user = c.var.user;
35-
console.log('uwu')
36-
this.testJob.queue('green');
37-
// this.testJob.worker();
3829
return c.json({ user: user });
3930
})
4031
.post('/login/request', zValidator('json', registerEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
4132
const { email } = c.req.valid('json');
42-
await this.loginRequestsService.create({ email });
33+
await this.iamService.createLoginRequest({ email });
4334
return c.json({ message: 'Verification email sent' });
4435
})
4536
.post('/login/verify', zValidator('json', signInEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
4637
const { email, token } = c.req.valid('json');
47-
const session = await this.loginRequestsService.verify({ email, token });
38+
const session = await this.iamService.verifyLoginRequest({ email, token });
4839
const sessionCookie = this.lucia.createSessionCookie(session.id);
4940
setCookie(c, sessionCookie.name, sessionCookie.value, {
5041
path: sessionCookie.attributes.path,
@@ -74,14 +65,14 @@ export class IamController implements Controller {
7465
})
7566
.patch('/email', requireAuth, zValidator('json', updateEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
7667
const json = c.req.valid('json');
77-
await this.emailVerificationsService.dispatchEmailVerificationRequest(c.var.user.id, json.email);
68+
await this.iamService.dispatchEmailVerificationRequest(c.var.user.id, json.email);
7869
return c.json({ message: 'Verification email sent' });
7970
})
8071
// this could also be named to use custom methods, aka /email#verify
8172
// https://cloud.google.com/apis/design/custom_methods
8273
.post('/email/verification', requireAuth, zValidator('json', verifyEmailDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
8374
const json = c.req.valid('json');
84-
await this.emailVerificationsService.processEmailVerificationRequest(c.var.user.id, json.token);
75+
await this.iamService.processEmailVerificationRequest(c.var.user.id, json.token);
8576
return c.json({ message: 'Verified and updated' });
8677
});
8778
}

src/lib/server/api/services/email-verifications.service.ts

Lines changed: 0 additions & 67 deletions
This file was deleted.

src/lib/server/api/services/iam.service.ts

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { LoginVerificationEmail } from '../emails/login-verification.email';
1010
import { DatabaseProvider } from '../providers/database.provider';
1111
import { BadRequest } from '../common/exceptions';
1212
import { WelcomeEmail } from '../emails/welcome.email';
13+
import { EmailVerificationsRepository } from '../repositories/email-verifications.repository';
14+
import { EmailChangeNoticeEmail } from '../emails/email-change-notice.email';
1315

1416
@injectable()
1517
export class IamService {
@@ -20,7 +22,7 @@ export class IamService {
2022
@inject(MailerService) private readonly mailerService: MailerService,
2123
@inject(UsersRepository) private readonly usersRepository: UsersRepository,
2224
@inject(LoginRequestsRepository) private readonly loginRequestsRepository: LoginRequestsRepository,
23-
25+
@inject(EmailVerificationsRepository) private readonly emailVerificationsRepository: EmailVerificationsRepository,
2426
) { }
2527

2628
async createLoginRequest(data: RegisterEmailDto) {
@@ -46,6 +48,41 @@ export class IamService {
4648
return this.lucia.createSession(existingUser.id, {});
4749
}
4850

51+
// These steps follow the process outlined in OWASP's "Changing A User's Email Address" guide.
52+
// https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#changing-a-users-registered-email-address
53+
async dispatchEmailVerificationRequest(userId: string, requestedEmail: string) {
54+
// generate a token and expiry
55+
const { token, expiry, hashedToken } = await this.tokensService.generateTokenWithExpiryAndHash(15, 'm')
56+
const user = await this.usersRepository.findOneByIdOrThrow(userId)
57+
58+
// create a new email verification record
59+
await this.emailVerificationsRepository.create({ requestedEmail, userId, hashedToken, expiresAt: expiry })
60+
61+
// A confirmation-required email message to the proposed new address, instructing the user to
62+
// confirm the change and providing a link for unexpected situations
63+
this.mailerService.send({
64+
to: requestedEmail,
65+
email: new LoginVerificationEmail(token)
66+
})
67+
68+
// A notification-only email message to the current address, alerting the user to the impending change and
69+
// providing a link for an unexpected situation.
70+
this.mailerService.send({
71+
to: user.email,
72+
email: new EmailChangeNoticeEmail()
73+
})
74+
}
75+
76+
async processEmailVerificationRequest(userId: string, token: string) {
77+
const validRecord = await this.findAndBurnEmailVerificationToken(userId, token)
78+
if (!validRecord) throw BadRequest('Invalid token');
79+
await this.usersRepository.update(userId, { email: validRecord.requestedEmail, verified: true });
80+
}
81+
82+
async logout(sessionId: string) {
83+
return this.lucia.invalidateSession(sessionId);
84+
}
85+
4986
// Create a new user and send a welcome email - or other onboarding process
5087
private async handleNewUserRegistration(email: string) {
5188
const newUser = await this.usersRepository.create({ email, verified: true })
@@ -71,7 +108,21 @@ export class IamService {
71108
})
72109
}
73110

74-
async logout(sessionId: string) {
75-
return this.lucia.invalidateSession(sessionId);
111+
private async findAndBurnEmailVerificationToken(userId: string, token: string) {
112+
return this.db.transaction(async (trx) => {
113+
// find a valid record
114+
const emailVerificationRecord = await this.emailVerificationsRepository.trxHost(trx).findValidRecord(userId);
115+
if (!emailVerificationRecord) return null;
116+
117+
// check if the token is valid
118+
const isValidRecord = await this.tokensService.verifyHashedToken(emailVerificationRecord.hashedToken, token);
119+
if (!isValidRecord) return null
120+
121+
// burn the token if it is valid
122+
await this.emailVerificationsRepository.trxHost(trx).deleteById(emailVerificationRecord.id)
123+
return emailVerificationRecord
124+
})
76125
}
126+
127+
77128
}

src/lib/server/api/services/login-requests.service.ts

Lines changed: 0 additions & 72 deletions
This file was deleted.

0 commit comments

Comments
 (0)