-
Notifications
You must be signed in to change notification settings - Fork 6
#42 authentication login #211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
f031b22
060bfa0
17ffef4
89e9b6b
ddb4edd
9f76ac2
0160876
859915b
e71256c
b322d6c
1540c66
b4420c3
ce6d32c
d257c4e
a7540c2
de98243
f327b9e
896f553
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| export class GenerateToken { | ||
| readonly email: string; | ||
| readonly password: string; | ||
|
|
||
| constructor(email: string, password: string) { | ||
| this.email = email; | ||
| this.password = password; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { CommandHandler } from '../../../../../shared/core/application/command/CommandHandler'; | ||
| import { DomainEventPublisher } from '../../../../../shared/core/application/event/DomainEventBus'; | ||
| import { CurrentTimeProvider } from '../../../../../shared/core/CurrentTimeProvider'; | ||
| import { AuthenticationRepository } from '../AuthenticationRepository'; | ||
| import { CommandResult } from '../../../../../shared/core/application/command/CommandResult'; | ||
| import { GenerateToken } from './GenerateToken'; | ||
| import { authenticateUser } from '../../domain/UserAccount'; | ||
| import { ITokenGenerator } from '../../../infrastructure/token/ITokenGenerator'; | ||
| import { IPasswordEncryptor } from '../../../infrastructure/password/IPasswordEncryptor'; | ||
|
|
||
| export class GenerateTokenCommandHandler implements CommandHandler<GenerateToken> { | ||
| constructor( | ||
| private readonly eventPublisher: DomainEventPublisher, | ||
| private readonly currentTimeProvider: CurrentTimeProvider, | ||
| private readonly repository: AuthenticationRepository, | ||
| private readonly tokenGenerator: ITokenGenerator, | ||
| private readonly passwordEncryptor: IPasswordEncryptor, | ||
| ) {} | ||
|
|
||
| async execute(command: GenerateToken): Promise<CommandResult> { | ||
| const userAccount = await this.repository.findByEmail(command.email); | ||
| let isPasswordCorrect = false; | ||
| let token = ''; | ||
| if (userAccount) { | ||
| isPasswordCorrect = await this.passwordEncryptor.comparePasswords(command.password, userAccount.password.raw); | ||
| token = this.tokenGenerator.generateToken(command.email, userAccount.userId.raw); | ||
| } | ||
| const { state, events } = await authenticateUser(userAccount, command, isPasswordCorrect, token, this.currentTimeProvider()); | ||
| this.eventPublisher.publishAll(events); | ||
| return CommandResult.success(state); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,9 @@ | ||
| export class SetPassword { | ||
| readonly email: string; | ||
| readonly userId: string; | ||
| readonly password: string; | ||
|
|
||
| constructor(email: string, password: string) { | ||
| this.email = email; | ||
| constructor(userId: string, password: string) { | ||
| this.userId = userId; | ||
| this.password = password; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,17 +5,20 @@ import { CommandResult } from '../../../../../shared/core/application/command/Co | |
| import { SetPassword } from './SetPassword'; | ||
| import { AuthenticationRepository } from '../AuthenticationRepository'; | ||
| import { setPasswordForUserAccount } from '../../domain/UserAccount'; | ||
| import { IPasswordEncryptor } from '../../../infrastructure/password/IPasswordEncryptor'; | ||
|
|
||
| export class SetPasswordCommandHandler implements CommandHandler<SetPassword> { | ||
| constructor( | ||
| private readonly eventPublisher: DomainEventPublisher, | ||
| private readonly currentTimeProvider: CurrentTimeProvider, | ||
| private readonly repository: AuthenticationRepository, | ||
| private readonly passwordEncryptor: IPasswordEncryptor, | ||
| ) {} | ||
|
|
||
| async execute(command: SetPassword): Promise<CommandResult> { | ||
| const userAccount = await this.repository.findByEmail(command.email); | ||
| const { state, events } = setPasswordForUserAccount(userAccount, command, this.currentTimeProvider()); | ||
| const userAccount = await this.repository.findById(command.userId); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Czy kazdy user moze kazdemu ustawic haslo? Tutaj sie przyda sprawdzenie wlasnie aktualnego usera. Ale to moze byc kolejny PR :)
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. czyli, że w metodzie setPasswordForUserAccount by się przydało jeszcze sprawdzenie np. tokena? Jeśli tak to kumam o co biega, ale nie mam zielonego pojęcia jak to zrobić .... to by musiało iść przez ten middleware do sprawdzania poprawności tokena, a to trzeba osobno skodzić, Si?
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmmm... no to zalezy :P Najprosciej mozna to zrobic w metodzie execute, ale niezbyt to reuzywalne. Middleware wydaje sie lepszy. Chyba byly na kursach jakies takie rzeczy, poszukaj jak zrobic autoryzacje poprzez middleware. Ta autoryzacja mialaby tak dzialac, ze tylko user moze zmienic haslo dla samego siebie. |
||
| const hashedPassword = await this.passwordEncryptor.encryptPassword(command.password); | ||
| const { state, events } = await setPasswordForUserAccount(userAccount, command, hashedPassword, this.currentTimeProvider()); | ||
| await this.repository.save(state); | ||
| this.eventPublisher.publishAll(events); | ||
| return CommandResult.success(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| export class Email { | ||
| private readonly TYPE = 'Email'; | ||
|
|
||
| private constructor(readonly raw: string) {} | ||
|
|
||
| static from(email: string): Email { | ||
| if (email.length <= 0) { | ||
| throw new Error('Email cannot be empty!'); | ||
| } | ||
|
|
||
| const regexp = new RegExp( | ||
| /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, | ||
| ); | ||
| if (!regexp.test(email)) { | ||
| throw new Error('Email format is wrong!'); | ||
| } | ||
|
|
||
| return new Email(email); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| export class Password { | ||
| private readonly TYPE = 'Password'; | ||
|
|
||
| private constructor(readonly raw: string) {} | ||
|
|
||
| static from(password: string): Password { | ||
| if (password.length <= 5) { | ||
| throw new Error('Password cannot be shorter than 5 signs!'); | ||
| } | ||
|
|
||
| return new Password(password); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| export class UserId { | ||
| private readonly TYPE = 'UserId'; | ||
|
|
||
| private constructor(readonly raw: string) {} | ||
|
|
||
| static from(userId: string): UserId { | ||
| if (userId.length <= 0) { | ||
| throw new Error('UserId cannot be empty!'); | ||
| } | ||
| return new UserId(userId); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import { DomainEvent } from '../../../../../shared/domain/event/DomainEvent'; | ||
|
|
||
| export class TokenGenerated implements DomainEvent { | ||
| readonly occurredAt: Date; | ||
| readonly email: string; | ||
|
|
||
| constructor(props: { occurredAt: Date; email: string }) { | ||
| this.occurredAt = props.occurredAt; | ||
| this.email = props.email; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import { DomainEvent } from '../../../../../shared/domain/event/DomainEvent'; | ||
|
|
||
| export class TokenGenerationFailed implements DomainEvent { | ||
| readonly occurredAt: Date; | ||
| readonly email: string; | ||
|
|
||
| constructor(props: { occurredAt: Date; email: string }) { | ||
| this.occurredAt = props.occurredAt; | ||
| this.email = props.email; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| export interface IPasswordEncryptor { | ||
| encryptPassword(password: string): Promise<string>; | ||
|
|
||
| comparePasswords(firstPassword: string, secondPassword: string): Promise<boolean>; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import { IPasswordEncryptor } from './IPasswordEncryptor'; | ||
| import bcrypt from 'bcrypt'; | ||
|
|
||
| export class PasswordEncryptor implements IPasswordEncryptor { | ||
| async encryptPassword(password: string): Promise<string> { | ||
| return await bcrypt.hash(password, 12); | ||
| } | ||
|
|
||
| async comparePasswords(firstPassword: string, secondPassword: string): Promise<boolean> { | ||
| return await bcrypt.compare(firstPassword, secondPassword); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { AuthenticationRepository } from '../../../core/application/AuthenticationRepository'; | ||
| import { UserAccount } from '../../../core/domain/UserAccount'; | ||
|
|
||
| export class InMemoryAuthenticationRepository implements AuthenticationRepository { | ||
| private readonly entities: { [id: string]: UserAccount } = {}; | ||
|
|
||
| findByEmail(email: string): Promise<UserAccount | undefined> { | ||
| return Promise.resolve(Object.values(this.entities).find((userAccount) => userAccount.email.raw === email)); | ||
| } | ||
|
|
||
| findById(userId: string): Promise<UserAccount> { | ||
| return Promise.resolve(this.entities[userId]); | ||
| } | ||
|
|
||
| async save(userAccount: UserAccount): Promise<void> { | ||
| this.entities[userAccount.userId.raw] = userAccount; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| export interface ITokenGenerator { | ||
| generateToken(email: string, userId: string): string; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import { ITokenGenerator } from './ITokenGenerator'; | ||
| import jwt from 'jsonwebtoken'; | ||
|
|
||
| export class TokenGenerator implements ITokenGenerator { | ||
| generateToken(email: string, userId: string): string { | ||
| return jwt.sign({ email: email, userId: userId }, `${process.env.JWT_SECRET_KEY}`, { expiresIn: '1h' }); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aaa, w ogole kiedy to bedzie uzywane, mi przypomnij :) ? Jaki jest use case tego?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eee jeszcze nie, ale pewnie będzie ^_^, chyba fajnie mieć możliwość zmiany hasło :-P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
było w miro i to zaczęła Ania wgl robić, a ja tam trochę grzebię