Skip to content
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

feat(auth): Sign In, Sign Up, Reset Password #12

Merged
merged 108 commits into from
Aug 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
11bd6fe
feat(api): parse cookie
Krr0ptioN May 3, 2024
390aefc
feat(api): add users module to api
Krr0ptioN May 3, 2024
5e1b913
feat(data-acess): neon data acess
Krr0ptioN May 3, 2024
77b5c0f
feat(types): common types and abstraction library
Krr0ptioN May 3, 2024
c161d7d
feat(types): entity repository interface
Krr0ptioN May 3, 2024
1833d2b
feat(users): users entity management and mutation
Krr0ptioN May 3, 2024
0215d17
feat(config): server configuration library
Krr0ptioN May 3, 2024
8011b32
feat(security): core library for security measurements and metrics
Krr0ptioN May 3, 2024
53357eb
feat(security): api authentication, signup, signin, signout
Krr0ptioN May 3, 2024
67e7802
test: definable controllers for authentication module controllers
Krr0ptioN May 3, 2024
50ffee3
improve: exclude secret and critical informations from return type of…
Krr0ptioN May 4, 2024
1c100cd
chore(users): services mock for testing
Krr0ptioN May 4, 2024
3037b9c
remove: signout is useless and uncessary endpoint
Krr0ptioN May 5, 2024
320225f
improve: more meaningful service name
Krr0ptioN May 5, 2024
98980b1
chore: linting
Krr0ptioN May 5, 2024
497213f
feat: higher restrictive rules for signin and signup password property
Krr0ptioN May 5, 2024
638ffce
chore: linting
Krr0ptioN May 5, 2024
4909fd1
test(security): auth services
Krr0ptioN May 5, 2024
42720f1
chore: avoiding arrow functions in class
Krr0ptioN May 5, 2024
8a0f588
docs(security): password service password reset attempt verify
Krr0ptioN May 5, 2024
d776e7b
chore: correct naming
Krr0ptioN May 5, 2024
adfaac5
feat(mail): mail service with resend
Krr0ptioN May 6, 2024
376179a
feat(security): send mail for password reset confirmation
Krr0ptioN May 6, 2024
7ed6ca2
test(security): pasword service
Krr0ptioN May 6, 2024
20b4ce5
feat: api tags and metadata for swagger
Krr0ptioN May 6, 2024
95faf73
fix: add mail module to auth
Krr0ptioN May 6, 2024
1377b2f
feat(security): revoking tokens and determining whether token is revoked
Krr0ptioN May 6, 2024
e289499
test(security): token service
Krr0ptioN May 6, 2024
b487562
feat: shared utils library
Krr0ptioN May 6, 2024
57ced50
chore: eslint plugin for nestjs
Krr0ptioN May 6, 2024
7ebb9bd
chore: packages and configurations
Krr0ptioN May 6, 2024
0c6f615
refact: move the schemas of neon drizzle database access to one file
Krr0ptioN May 7, 2024
5c097e6
chore: reorganize package depenecies
Krr0ptioN May 7, 2024
42de05e
fix: linting
Krr0ptioN May 7, 2024
70a5bb1
fix: add dto validation pipe
Krr0ptioN May 8, 2024
c51199f
test(security): e2e user authentication and validation
Krr0ptioN May 8, 2024
45161e5
improve: hide user password and sensitive data
Krr0ptioN May 8, 2024
bfcdfd6
fix: import staging environment varbiable
Krr0ptioN May 8, 2024
75d40b8
refact(ddd): moving controllers and dtos to presenter layer
Krr0ptioN Jun 20, 2024
e2c6cd1
refact(ddd): moving repos and db to infrastructure layer
Krr0ptioN Jun 20, 2024
6ed6356
refact(ddd): common utilities for ddd and new architecture
Krr0ptioN Jun 20, 2024
0a0a7c4
refact(ddd): CQRS over CRUD
Krr0ptioN Jun 20, 2024
514d0c0
refact(ddd): user entity
Krr0ptioN Jun 20, 2024
ae304df
improve: encapsulating global registers and settings from main bootst…
Krr0ptioN Jun 20, 2024
4e5079e
improve: logging application running url and documention url
Krr0ptioN Jun 20, 2024
2f7f7fe
refact(data-access-drizzle): renaming data access library for drizzle
Krr0ptioN Jun 20, 2024
b39a877
refact(ddd): top level folder structure to ddd
Krr0ptioN Jun 20, 2024
80b1ddb
chore(api-process): adding a nice banner to api execution process
Krr0ptioN Jun 26, 2024
ccc6a32
chore: register globals are async
Krr0ptioN Jun 26, 2024
5ea32ef
chore: package update
Krr0ptioN Jun 26, 2024
fb46e15
chore: remove uncessary library import alias
Krr0ptioN Jul 3, 2024
13ed667
docs(api): global pipe and interceptors registers util function
Krr0ptioN Jul 25, 2024
0186798
improve(api): server port and address config variable
Krr0ptioN Jul 25, 2024
4269161
fix(api-swagger): description
Krr0ptioN Jul 25, 2024
0f01af9
improve: adding scope to the logger in boostrap function
Krr0ptioN Jul 28, 2024
1aeecb6
docs: infra
Krr0ptioN Jul 28, 2024
a5d7c93
feat(users-management): change user password
Krr0ptioN Jul 29, 2024
8251fcd
feat(users-management): change user username
Krr0ptioN Jul 29, 2024
dd6f039
feat(users-management): create user command
Krr0ptioN Jul 29, 2024
37bbc12
feat(users-management): delete user command
Krr0ptioN Jul 29, 2024
c6e77ad
feat(users-management): change user email command
Krr0ptioN Jul 29, 2024
140dc20
remove(users-management): password reset request command
Krr0ptioN Jul 29, 2024
225e84d
remove(users-management): legacy code
Krr0ptioN Jul 29, 2024
18039ad
improve(users-management): transitioning from uuid to ulid
Krr0ptioN Jul 29, 2024
2aea933
add(users-management): users repository port
Krr0ptioN Jul 29, 2024
d5996b4
refact(users-management): using the service as a facade
Krr0ptioN Jul 29, 2024
4294c26
refact(users-management): moving users module to one level upper in f…
Krr0ptioN Jul 29, 2024
0c093db
add(users-management): password value object
Krr0ptioN Jul 29, 2024
c748e38
fix(users-management): conflict with database data type representatio…
Krr0ptioN Jul 29, 2024
844581e
chore(users-management): testing configuration for library
Krr0ptioN Jul 29, 2024
1d5484e
add(users-management): username already ergistered and username or em…
Krr0ptioN Jul 29, 2024
ba2a0f1
remove(ddd-lib): correlation tracking of requests
Krr0ptioN Jul 29, 2024
da5fc40
feat(utils): array pagination
Krr0ptioN Jul 29, 2024
acffd82
fix(pagination): overridable paginated response object
Krr0ptioN Jul 29, 2024
f351c4f
remove(ddd-lib): legacy repository port setup and types
Krr0ptioN Jul 29, 2024
1fc53a0
chore: adding packages
Krr0ptioN Jul 29, 2024
bd7dbf7
feat(security-authentication): signin command
Krr0ptioN Jul 29, 2024
5eeaf4a
feat(security-authentication): signup command
Krr0ptioN Jul 29, 2024
5a40058
chore(security-password-reset): command setup for requesting, verifyi…
Krr0ptioN Jul 29, 2024
81fc3ad
fixup! feat(security-authentication): signup command
Krr0ptioN Jul 29, 2024
49b4cba
fixup! feat(security-authentication): signin command
Krr0ptioN Jul 29, 2024
465cee2
fix: make fullname optional in SignupCommand
Krr0ptioN Jul 29, 2024
0ffee33
refactor: simplify token validation in LocalAuthGuard
Krr0ptioN Jul 29, 2024
fd3a87f
refactor: simplify token service methods and imports
Krr0ptioN Jul 29, 2024
1c4d72a
refactor: simplify error handling in FindOneUserByIdQueryHandler
Krr0ptioN Jul 29, 2024
2ef4d34
fix: make fullname optional in SignUpDto
Krr0ptioN Jul 29, 2024
418c37d
refactor(authentication-controller): using command bus instead of ser…
Krr0ptioN Jul 29, 2024
bc9af6b
feat(nx-cloud): setup nx cloud workspace (#13)
Krr0ptioN Jul 29, 2024
b6c9bd2
feat(user-id-request-context): get user id from request context
Krr0ptioN Aug 1, 2024
ca165c6
chore: moving the auth lib to its correct location in library
Krr0ptioN Aug 1, 2024
3ac9ff4
chore(session-management): init module
Krr0ptioN Aug 1, 2024
16edfc1
fix: circular imports
Krr0ptioN Aug 16, 2024
ac2a009
refact(infra/drizzle-insertone): if-failed intsead of if-else
Krr0ptioN Aug 16, 2024
89d91bd
feat(users/errors): user deletion and creation failure errors
Krr0ptioN Aug 16, 2024
4fdfc7b
chore: adding missing providers and imports
Krr0ptioN Aug 16, 2024
1099700
refact: using dto instead of value object
Krr0ptioN Aug 17, 2024
f069da7
refact: drizzle and database implementation
Krr0ptioN Aug 17, 2024
b7b9622
refact: simplifying props passing
Krr0ptioN Aug 17, 2024
1fd8d77
fix: async services
Krr0ptioN Aug 17, 2024
3a9dded
chore: implementing validation (no-rules)
Krr0ptioN Aug 17, 2024
52d0675
refact: sending back dto instead of value object
Krr0ptioN Aug 17, 2024
8a59143
chore: missing handlers and cqrs command bus
Krr0ptioN Aug 17, 2024
9197489
refact(otp-password-reset): controllers and dtos
Krr0ptioN Aug 17, 2024
03b5e18
chore: changing to yarn
Krr0ptioN Aug 17, 2024
dc13a1e
chore: error free and skipping
Krr0ptioN Aug 17, 2024
b0868af
chore: bump up versions
Krr0ptioN Aug 17, 2024
4b9447d
ci: pnpm to yarn and diving tasks into jobs
Krr0ptioN Aug 17, 2024
abe2c43
fix(mail): temp fix and passing
Krr0ptioN Aug 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,22 @@
"ignorePatterns": ["**/*"],
"plugins": ["@nx"],
"overrides": [
{
"files": ["*.ts", "*.js"],
"extends": ["plugin:@darraghor/nestjs-typed/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": ["./tsconfig.base.json"]
},
"plugins": ["@darraghor/nestjs-typed"],
"rules": {
"@darraghor/nestjs-typed/injectable-should-be-provided": "off"
}
},
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
"indent": ["error", 2],
"@nx/enforce-module-boundaries": [
"error",
{
Expand Down
57 changes: 43 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,58 @@ permissions:
contents: read

jobs:
main:
lint:
environment: staging
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v3
with:
node-version: 20
cache: 'yarn'
- run: yarn install --frozen-lockfile
- run: yarn nx affected -t lint

# Connect your workspace on nx.app and uncomment this to enable task distribution.
# The "--stop-agents-after" is optional, but allows idle agents to shut down once the "build" targets have been requested
# - run: pnpm exec nx-cloud start-ci-run --distribute-on="5 linux-medium-js" --stop-agents-after="build"

- uses: pnpm/action-setup@v2
test:
environment: staging
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
version: 8
# Cache node_modules
fetch-depth: 0
- uses: actions/setup-node@v3
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- uses: nrwl/nx-set-shas@v4
cache: 'yarn'
- run: yarn install --frozen-lockfile
- run: yarn nx affected -t test

- run: git branch --track main origin/main
if: ${{ github.event_name == 'pull_request' }}
build:
environment: staging
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v3
with:
node-version: 20
cache: 'yarn'
- run: yarn install --frozen-lockfile
- run: yarn nx affected -t build

- run: pnpm exec nx affected -t lint test builde e2e
e2e:
environment: staging
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v3
with:
node-version: 20
cache: 'yarn'
- run: yarn install --frozen-lockfile
- run: yarn nx affected -t e2e
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,7 @@ Thumbs.db

.nx/cache
.env.local.development
.env.*
.env
.vercel
infra/postgres
2 changes: 1 addition & 1 deletion apps/api-e2e/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"ignorePatterns": ["**/webpack.*.json", "**/node_modules/", "!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
Expand Down
92 changes: 92 additions & 0 deletions apps/api-e2e/src/api/auth.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { USERS_REPOSITORY } from '@goran/users';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import { Test } from '@nestjs/testing';

/* eslint-disable @nx/enforce-module-boundaries */
import { AppModule } from 'apps/api/src/app/app.module';
import { MockUsersRepository } from 'libs/domain/users/src/repositories/user-mock.repository';
/* eslint-enable @nx/enforce-module-boundaries */

import request from 'supertest';

const user = {
email: 'rogan@goran.com',
username: 'rogan',
fullname: 'Joe Rogan',
password: 'JoeR0gan',
};

describe('Authentication /api/auth', () => {
let app: INestApplication;

beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
})
.overrideProvider(USERS_REPOSITORY)
.useClass(MockUsersRepository)
.compile();
app = moduleRef.createNestApplication();
app.useGlobalPipes(new ValidationPipe());
await app.init();
});

test(`User registeration`, async () => {
const res = await request(app.getHttpServer())
.post('/auth/signup')
.send(user);
expect(res.body.data.tokens).toBeDefined();
expect(res.body.data.user.email).toEqual(user.email);
expect(res.body.data.user.username).toEqual(user.username);
expect(res.body.data.user.password).not.toBeDefined();
expect(res.status).toEqual(201);
});

it(`should not signup user with weak password`, async () => {
const res = await request(app.getHttpServer())
.post('/auth/signup')
.send({ ...user, password: 'weakpass' });
expect(res.body.error).toMatch('Bad Request');
expect(res.body.message).toEqual([
'Password must contain at least one lowercase letter, one uppercase letter, and one digit',
]);
expect(res.body.statusCode).toEqual(400);
});

it(`login using email`, async () => {
const res = await request(app.getHttpServer())
.post('/auth/signin')
.send({ email: user.email, password: user.password });
expect(res.body.data.tokens).toBeDefined();
expect(res.body.data.user.email).toEqual(user.email);
expect(res.body.data.user.username).toEqual(user.username);
expect(res.body.data.user.password).not.toBeDefined();
expect(res.status).toEqual(201);
});

it(`login using username`, async () => {
const res = await request(app.getHttpServer())
.post('/auth/signin')
.send({ username: user.username, password: user.password });
expect(res.body.data.tokens).toBeDefined();
expect(res.body.data.user.email).toEqual(user.email);
expect(res.body.data.user.username).toEqual(user.username);
expect(res.body.data.user.password).not.toBeDefined();
expect(res.status).toEqual(201);
});

it(`should not login with invalid password`, async () => {
const res = await request(app.getHttpServer())
.post('/auth/signin')
.send({ email: user.email, password: user.password + 'invalid' });
expect(res.body.error).toBeDefined();
expect(res.body.message).toEqual(
'User not found or the provided credential is invalid.'
);
expect(res.status).toEqual(400);
});

afterAll(async () => {
await app.close();
});
});
6 changes: 5 additions & 1 deletion apps/api/src/app/app.controller.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { Controller, Get } from '@nestjs/common';

import { AppService } from './app.service';
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';

@ApiTags()
@Controller()
export class AppController {
constructor(private readonly appService: AppService) { }

@ApiOkResponse()
@Get('health')
getHealth() {
return this.appService.getHealthStatus();
}

@ApiOkResponse()
@Get()
getData() {
return this.appService.getData();
Expand Down
29 changes: 26 additions & 3 deletions apps/api/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,34 @@
import { Module } from '@nestjs/common';

import { ConfigModule, ConfigService } from '@nestjs/config';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CONFIG_APP, configSchema } from '@goran/config';
import { UsersModule } from '@goran/users';
import { AuthenticationModule } from '@goran/security';
import { DatabaseModule } from '@goran/drizzle-data-access';

@Module({
imports: [],
imports: [
DatabaseModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
host: configService.get(CONFIG_APP.DB_HOST),
port: configService.get(CONFIG_APP.DB_PORT),
user: configService.get(CONFIG_APP.DB_USER),
password: configService.get(CONFIG_APP.DB_PASSWORD),
database: configService.get(CONFIG_APP.DB_DATABASE),
}),
}),
ConfigModule.forRoot({
isGlobal: true,
envFilePath: ['.env', '.env.local'],
validationSchema: configSchema,
}),
UsersModule,
AuthenticationModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
export class AppModule { }
21 changes: 21 additions & 0 deletions apps/api/src/globals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {
ClassSerializerInterceptor,
INestApplication,
ValidationPipe,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';

/**
* Registers global pipes and interceptors, plus server conifguration
*
* @param app - Nestjs application object
* @returns Modified and configured nestjs application object and certain parameters
*/
export async function registerGlobals(app: INestApplication) {
const globalPrefix = 'api';
app.setGlobalPrefix(globalPrefix);
app.useGlobalPipes(new ValidationPipe());
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));

return { app, globalPrefix };
}
25 changes: 18 additions & 7 deletions apps/api/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
import { Logger } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';

import { CONFIG_APP } from '@goran/config';
import { AppModule } from './app/app.module';
import { registerGlobals } from './globals';
import { setupSwagger } from './swagger';
import { goranBanner } from '@goran/utils';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
const globalPrefix = 'api';
app.setGlobalPrefix(globalPrefix);
const port = process.env.PORT || 3000;
await app.listen(port);
Logger.log(
`🚀 Application is running on: http://localhost:${port}/${globalPrefix}`
const logger = new Logger('BOOTSTRAP');
const port = process.env[CONFIG_APP.SERVER_PORT] || 3000;

const { globalPrefix } = await registerGlobals(app);
setupSwagger(app);

logger.log(goranBanner);
logger.log(
` Application is running on: http://localhost:${port}/${globalPrefix}`
);
logger.log(
` Documentation is running on: http://localhost:${port}/${globalPrefix}/docs`
);

await app.listen(port);
}

bootstrap();
12 changes: 12 additions & 0 deletions apps/api/src/swagger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { INestApplication } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';

export function setupSwagger(app: INestApplication) {
const config = new DocumentBuilder()
.setTitle('Goran API')
.setDescription('Goran REST-API')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('docs', app, document);
}
28 changes: 14 additions & 14 deletions apps/api/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ const { NxWebpackPlugin } = require('@nx/webpack');
const { join } = require('path');

module.exports = {
output: {
path: join(__dirname, '../../dist/apps/api'),
},
plugins: [
new NxWebpackPlugin({
target: 'node',
compiler: 'tsc',
main: './src/main.ts',
tsConfig: './tsconfig.app.json',
assets: ['./src/assets'],
optimization: false,
outputHashing: 'none',
}),
],
output: {
path: join(__dirname, '../../dist/apps/api'),
},
plugins: [
new NxWebpackPlugin({
target: 'node',
compiler: 'tsc',
main: './src/main.ts',
tsConfig: './tsconfig.app.json',
assets: ['./src/assets'],
optimization: false,
outputHashing: 'none',
}),
],
};
Empty file added infra/README.md
Empty file.
18 changes: 18 additions & 0 deletions libs/core/config/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["../../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
9 changes: 9 additions & 0 deletions libs/core/config/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "config",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/core/config/src",
"projectType": "library",
"tags": [],
"// targets": "to see all targets run: nx show project config --web",
"targets": {}
}
1 change: 1 addition & 0 deletions libs/core/config/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './schema';
Loading
Loading