@mshima/generator-jhipster-nodejs 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +204 -0
- package/README.md +243 -2
- package/build-info.json +3 -0
- package/cli/cli-customizations.cjs +9 -0
- package/cli/cli.cjs +40 -0
- package/cli/logo.js +31 -0
- package/cli/version.cjs +12 -0
- package/generators/ci-cd/USAGE +5 -0
- package/generators/ci-cd/command.js +5 -0
- package/generators/ci-cd/generator.js +158 -0
- package/generators/ci-cd/index.js +2 -0
- package/generators/ci-cd/templates/.gitlab-ci.yml.ejs +137 -0
- package/generators/ci-cd/templates/azure-pipelines.yml.ejs +68 -0
- package/generators/ci-cd/templates/circle.yml.ejs +59 -0
- package/generators/ci-cd/templates/docker-registry.yml.ejs +58 -0
- package/generators/ci-cd/templates/github-ci.yml.ejs +90 -0
- package/generators/ci-cd/templates/jenkins/Jenkinsfile.ejs +104 -0
- package/generators/ci-cd/templates/jenkins/idea.gdsl +90 -0
- package/generators/ci-cd/templates/jenkins/jenkins.yml.ejs +33 -0
- package/generators/ci-cd/templates/travis.yml.ejs +75 -0
- package/generators/client/command.js +5 -0
- package/generators/client/generator.js +21 -0
- package/generators/client/generators/bootstrap/command.js +5 -0
- package/generators/client/generators/bootstrap/generator.js +28 -0
- package/generators/client/generators/bootstrap/index.js +2 -0
- package/generators/client/index.js +2 -0
- package/generators/common/generators/bootstrap/command.js +5 -0
- package/generators/common/generators/bootstrap/generator.js +37 -0
- package/generators/common/generators/bootstrap/index.js +2 -0
- package/generators/docker/command.js +5 -0
- package/generators/docker/generator.js +16 -0
- package/generators/docker/generators/bootstrap/command.js +5 -0
- package/generators/docker/generators/bootstrap/generator.js +56 -0
- package/generators/docker/generators/bootstrap/index.js +2 -0
- package/generators/docker/index.js +2 -0
- package/generators/docker/templates/docker/app.yml.ejs +25 -0
- package/generators/generator-nodejs-constants.js +32 -0
- package/generators/languages/command.js +5 -0
- package/generators/languages/generator.js +22 -0
- package/generators/languages/index.js +2 -0
- package/generators/languages/templates/src/main/webapp/i18n/en/home.json +19 -0
- package/generators/languages/templates/src/main/webapp/i18n/es/home.json +19 -0
- package/generators/languages/templates/src/main/webapp/i18n/it/home.json +19 -0
- package/generators/node-server/command.js +96 -0
- package/generators/node-server/entity-files.js +41 -0
- package/generators/node-server/files.js +254 -0
- package/generators/node-server/generator.js +702 -0
- package/generators/node-server/generators/bootstrap/command.js +5 -0
- package/generators/node-server/generators/bootstrap/generator.js +83 -0
- package/generators/node-server/generators/bootstrap/index.js +2 -0
- package/generators/node-server/index.js +2 -0
- package/generators/node-server/resources/package.json +76 -0
- package/generators/node-server/support/api-fake-data.js +54 -0
- package/generators/node-server/support/columns.js +73 -0
- package/generators/node-server/support/database.js +24 -0
- package/generators/node-server/support/entity-authorities.js +32 -0
- package/generators/node-server/support/entity-paths.js +13 -0
- package/generators/node-server/support/enums.js +11 -0
- package/generators/node-server/support/index.js +9 -0
- package/generators/node-server/support/mail-templates.js +82 -0
- package/generators/node-server/support/migrations.js +197 -0
- package/generators/node-server/support/validators.js +25 -0
- package/generators/node-server/templates/.dockerignore.ejs +16 -0
- package/generators/node-server/templates/.gitignore.jhi.nodejs.ejs +24 -0
- package/generators/node-server/templates/Dockerfile.ejs +54 -0
- package/generators/node-server/templates/README.md.jhi.nodejs.ejs +220 -0
- package/generators/node-server/templates/package.json.ejs +84 -0
- package/generators/node-server/templates/server/README.md.ejs +269 -0
- package/generators/node-server/templates/server/e2e/_entityFileName_.database.e2e-spec.ts.ejs +192 -0
- package/generators/node-server/templates/server/e2e/_entityFileName_.e2e-spec.ts.ejs +402 -0
- package/generators/node-server/templates/server/e2e/account.e2e-spec.ts.ejs +349 -0
- package/generators/node-server/templates/server/e2e/app.e2e-spec.ts.ejs +188 -0
- package/generators/node-server/templates/server/e2e/authority.e2e-spec.ts.ejs +50 -0
- package/generators/node-server/templates/server/e2e/cors.e2e-spec.ts.ejs +110 -0
- package/generators/node-server/templates/server/e2e/management.e2e-spec.ts.ejs +180 -0
- package/generators/node-server/templates/server/e2e/oauth2-bearer.e2e-spec.ts.ejs +71 -0
- package/generators/node-server/templates/server/e2e/session.e2e-spec.ts.ejs +219 -0
- package/generators/node-server/templates/server/e2e/setup.test.js.ejs +1 -0
- package/generators/node-server/templates/server/e2e/user.e2e-spec.ts.ejs +318 -0
- package/generators/node-server/templates/server/env.ejs +1 -0
- package/generators/node-server/templates/server/eslint.config.mjs.ejs +48 -0
- package/generators/node-server/templates/server/nest-cli.json.ejs +11 -0
- package/generators/node-server/templates/server/ormconfig.ts.ejs +35 -0
- package/generators/node-server/templates/server/package.json.ejs +143 -0
- package/generators/node-server/templates/server/sonar-project.properties.ejs +10 -0
- package/generators/node-server/templates/server/src/app.config.spec.ts.ejs +13 -0
- package/generators/node-server/templates/server/src/app.config.ts.ejs +179 -0
- package/generators/node-server/templates/server/src/app.module.ts.ejs +80 -0
- package/generators/node-server/templates/server/src/application-config.ts.ejs +153 -0
- package/generators/node-server/templates/server/src/auth/account.controller.ts.ejs +183 -0
- package/generators/node-server/templates/server/src/auth/auth.module.ts.ejs +104 -0
- package/generators/node-server/templates/server/src/auth/auth.service.ts.ejs +294 -0
- package/generators/node-server/templates/server/src/auth/csrf.middleware.spec.ts.ejs +65 -0
- package/generators/node-server/templates/server/src/auth/csrf.middleware.ts.ejs +76 -0
- package/generators/node-server/templates/server/src/auth/decorators/auth-user.decorator.ts.ejs +8 -0
- package/generators/node-server/templates/server/src/auth/decorators/roles.decorator.ts.ejs +4 -0
- package/generators/node-server/templates/server/src/auth/dto/key-and-password.dto.ts.ejs +17 -0
- package/generators/node-server/templates/server/src/auth/dto/password-change.dto.ts.ejs +17 -0
- package/generators/node-server/templates/server/src/auth/dto/user-login.dto.ts.ejs +21 -0
- package/generators/node-server/templates/server/src/auth/guards/auth.guard.ts.ejs +50 -0
- package/generators/node-server/templates/server/src/auth/guards/oauth2-login.guard.ts.ejs +6 -0
- package/generators/node-server/templates/server/src/auth/guards/roles.guard.ts.ejs +25 -0
- package/generators/node-server/templates/server/src/auth/index.ts.ejs +8 -0
- package/generators/node-server/templates/server/src/auth/jwt.config.spec.ts.ejs +22 -0
- package/generators/node-server/templates/server/src/auth/jwt.config.ts.ejs +29 -0
- package/generators/node-server/templates/server/src/auth/oauth2.authorities.spec.ts.ejs +24 -0
- package/generators/node-server/templates/server/src/auth/oauth2.authorities.ts.ejs +33 -0
- package/generators/node-server/templates/server/src/auth/oauth2.config.ts.ejs +51 -0
- package/generators/node-server/templates/server/src/auth/passport.jwt.strategy.ts.ejs +45 -0
- package/generators/node-server/templates/server/src/auth/passport.oauth2-bearer.strategy.ts.ejs +32 -0
- package/generators/node-server/templates/server/src/auth/passport.oauth2.strategy.spec.ts.ejs +27 -0
- package/generators/node-server/templates/server/src/auth/passport.oauth2.strategy.ts.ejs +44 -0
- package/generators/node-server/templates/server/src/auth/password-util.ts.ejs +10 -0
- package/generators/node-server/templates/server/src/auth/password.service.ts.ejs +34 -0
- package/generators/node-server/templates/server/src/auth/payload.interface.ts.ejs +14 -0
- package/generators/node-server/templates/server/src/auth/role-type.ts.ejs +7 -0
- package/generators/node-server/templates/server/src/auth/session-user.middleware.ts.ejs +33 -0
- package/generators/node-server/templates/server/src/auth/session.entity.ts.ejs +36 -0
- package/generators/node-server/templates/server/src/auth/session.store.spec.ts.ejs +99 -0
- package/generators/node-server/templates/server/src/auth/session.store.ts.ejs +116 -0
- package/generators/node-server/templates/server/src/auth/user.jwt.controller.ts.ejs +26 -0
- package/generators/node-server/templates/server/src/auth/user.oauth2.controller.ts.ejs +78 -0
- package/generators/node-server/templates/server/src/auth/user.session.controller.ts.ejs +103 -0
- package/generators/node-server/templates/server/src/common/common.module.ts.ejs +19 -0
- package/generators/node-server/templates/server/src/common/database/base.entity.ts.ejs +13 -0
- package/generators/node-server/templates/server/src/common/database/column-transformers.spec.ts.ejs +46 -0
- package/generators/node-server/templates/server/src/common/database/column-transformers.ts.ejs +69 -0
- package/generators/node-server/templates/server/src/common/database/column-types.spec.ts.ejs +21 -0
- package/generators/node-server/templates/server/src/common/database/column-types.ts.ejs +29 -0
- package/generators/node-server/templates/server/src/common/database/pagination.entity.spec.ts.ejs +66 -0
- package/generators/node-server/templates/server/src/common/database/pagination.entity.ts.ejs +108 -0
- package/generators/node-server/templates/server/src/common/dto/base.dto.ts.ejs +19 -0
- package/generators/node-server/templates/server/src/common/errors/error-constants.ts.ejs +10 -0
- package/generators/node-server/templates/server/src/common/errors/problem-details.filter.ts.ejs +63 -0
- package/generators/node-server/templates/server/src/common/errors/problem.exception.ts.ejs +92 -0
- package/generators/node-server/templates/server/src/common/field_validators.ejs +77 -0
- package/generators/node-server/templates/server/src/common/header-util.ts.ejs +105 -0
- package/generators/node-server/templates/server/src/common/interceptors/logging.interceptor.spec.ts.ejs +93 -0
- package/generators/node-server/templates/server/src/common/interceptors/logging.interceptor.ts.ejs +48 -0
- package/generators/node-server/templates/server/src/common/logging.spec.ts.ejs +83 -0
- package/generators/node-server/templates/server/src/common/logging.ts.ejs +129 -0
- package/generators/node-server/templates/server/src/common/metrics.spec.ts.ejs +43 -0
- package/generators/node-server/templates/server/src/common/metrics.ts.ejs +198 -0
- package/generators/node-server/templates/server/src/common/request.ts.ejs +8 -0
- package/generators/node-server/templates/server/src/common/sortable-properties.ts.ejs +43 -0
- package/generators/node-server/templates/server/src/common/updatable-properties.ts.ejs +14 -0
- package/generators/node-server/templates/server/src/config/application-dev.yml.ejs +118 -0
- package/generators/node-server/templates/server/src/config/application-prod.yml.ejs +97 -0
- package/generators/node-server/templates/server/src/config/application-secret-samples.yml.ejs +42 -0
- package/generators/node-server/templates/server/src/config/application-test.yml.ejs +68 -0
- package/generators/node-server/templates/server/src/config/application-tls-samples.yml.ejs +15 -0
- package/generators/node-server/templates/server/src/config/application-tls.yml.ejs +15 -0
- package/generators/node-server/templates/server/src/config/application.yml.ejs +149 -0
- package/generators/node-server/templates/server/src/config.spec.ts.ejs +81 -0
- package/generators/node-server/templates/server/src/config.ts.ejs +255 -0
- package/generators/node-server/templates/server/src/entities/_entityFileName_/_entityFileName_.controller.ts.ejs +178 -0
- package/generators/node-server/templates/server/src/entities/_entityFileName_/_entityFileName_.entity.ts.ejs +110 -0
- package/generators/node-server/templates/server/src/entities/_entityFileName_/_entityFileName_.mapper.ts.ejs +37 -0
- package/generators/node-server/templates/server/src/entities/_entityFileName_/_entityFileName_.module.ts.ejs +14 -0
- package/generators/node-server/templates/server/src/entities/_entityFileName_/_entityFileName_.service.ts.ejs +123 -0
- package/generators/node-server/templates/server/src/entities/_entityFileName_/dto/_entityFileName_.dto.ts.ejs +99 -0
- package/generators/node-server/templates/server/src/mail/mail.module.ts.ejs +49 -0
- package/generators/node-server/templates/server/src/mail/mail.service.spec.ts.ejs +105 -0
- package/generators/node-server/templates/server/src/mail/mail.service.ts.ejs +120 -0
- package/generators/node-server/templates/server/src/main.ts.ejs +114 -0
- package/generators/node-server/templates/server/src/management/api-docs.controller.ts.ejs +27 -0
- package/generators/node-server/templates/server/src/management/api-docs.service.ts.ejs +8 -0
- package/generators/node-server/templates/server/src/management/configuration.controller.ts.ejs +80 -0
- package/generators/node-server/templates/server/src/management/health.controller.spec.ts.ejs +79 -0
- package/generators/node-server/templates/server/src/management/health.controller.ts.ejs +87 -0
- package/generators/node-server/templates/server/src/management/health.module.ts.ejs +9 -0
- package/generators/node-server/templates/server/src/management/loggers.controller.ts.ejs +40 -0
- package/generators/node-server/templates/server/src/management/management.controller.spec.ts.ejs +26 -0
- package/generators/node-server/templates/server/src/management/management.controller.ts.ejs +34 -0
- package/generators/node-server/templates/server/src/management/management.module.ts.ejs +21 -0
- package/generators/node-server/templates/server/src/management/metrics.controller.ts.ejs +85 -0
- package/generators/node-server/templates/server/src/migrations/1570200270081-CreateTables.ts.ejs +64 -0
- package/generators/node-server/templates/server/src/migrations/1570200270082-CreateSessions.ts.ejs +26 -0
- package/generators/node-server/templates/server/src/migrations/1570200490072-SeedUsersRoles.ts.ejs +74 -0
- package/generators/node-server/templates/server/src/migrations/_entityConstraintsMigration_.ts.ejs +40 -0
- package/generators/node-server/templates/server/src/migrations/_entityMigration_.ts.ejs +26 -0
- package/generators/node-server/templates/server/src/migrations/support/schema.ts.ejs +202 -0
- package/generators/node-server/templates/server/src/orm.config.ts.ejs +147 -0
- package/generators/node-server/templates/server/src/registry/consul.service.ts.ejs +70 -0
- package/generators/node-server/templates/server/src/registry/eureka.service.ts.ejs +129 -0
- package/generators/node-server/templates/server/src/swagger.ts.ejs +24 -0
- package/generators/node-server/templates/server/src/tls.ts.ejs +27 -0
- package/generators/node-server/templates/server/src/users/authority.controller.ts.ejs +85 -0
- package/generators/node-server/templates/server/src/users/authority.entity.ts.ejs +22 -0
- package/generators/node-server/templates/server/src/users/dto/authority.dto.ts.ejs +10 -0
- package/generators/node-server/templates/server/src/users/dto/user.dto.ts.ejs +116 -0
- package/generators/node-server/templates/server/src/users/public.user.controller.ts.ejs +51 -0
- package/generators/node-server/templates/server/src/users/user.controller.ts.ejs +191 -0
- package/generators/node-server/templates/server/src/users/user.entity.ts.ejs +63 -0
- package/generators/node-server/templates/server/src/users/user.mapper.ts.ejs +26 -0
- package/generators/node-server/templates/server/src/users/user.module.ts.ejs +19 -0
- package/generators/node-server/templates/server/src/users/user.service.ts.ejs +156 -0
- package/generators/node-server/templates/server/tsconfig.build.json.ejs +9 -0
- package/generators/node-server/templates/server/tsconfig.json.ejs +23 -0
- package/generators/node-server/templates/server/typeorm-migration.ts.ejs +18 -0
- package/generators/node-server/templates/server/vitest.config.e2e.ts.ejs +16 -0
- package/generators/node-server/templates/server/vitest.config.ts.ejs +16 -0
- package/generators/server/command.js +7 -0
- package/generators/server/generator.js +20 -0
- package/generators/server/generators/bootstrap/command.js +5 -0
- package/generators/server/generators/bootstrap/generator.js +15 -0
- package/generators/server/generators/bootstrap/index.js +2 -0
- package/generators/server/index.js +2 -0
- package/package.json +75 -4
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<%_ if (authenticationType === 'oauth2') { _%>
|
|
2
|
+
import { HttpModule } from '@nestjs/axios';
|
|
3
|
+
<%_ } _%>
|
|
4
|
+
import { <% if (authenticationUsesCsrf) { %>MiddlewareConsumer, NestModule, <% } %>Module } from '@nestjs/common';
|
|
5
|
+
import { AuthService } from './auth.service.ts';
|
|
6
|
+
<%_ if (generateBuiltInUserEntity) { _%>
|
|
7
|
+
import { UserModule } from '../users/user.module.ts';
|
|
8
|
+
<%_ } _%>
|
|
9
|
+
import { PassportModule } from '@nestjs/passport';
|
|
10
|
+
<%_ if (authenticationType === 'jwt') { _%>
|
|
11
|
+
<%_ if (generateBuiltInUserEntity) { _%>
|
|
12
|
+
import { JwtModule } from '@nestjs/jwt';
|
|
13
|
+
import { UserJWTController} from './user.jwt.controller.ts';
|
|
14
|
+
<%_ } _%>
|
|
15
|
+
import { JwtStrategy } from './passport.jwt.strategy.ts';
|
|
16
|
+
<%_ } else if (authenticationType === 'oauth2') { _%>
|
|
17
|
+
import { Oauth2Strategy } from './passport.oauth2.strategy.ts';
|
|
18
|
+
import { Oauth2BearerStrategy } from './passport.oauth2-bearer.strategy.ts';
|
|
19
|
+
import { UserOauth2Controller } from './user.oauth2.controller.ts';
|
|
20
|
+
<%_ } else if (authenticationTypeSession) { _%>
|
|
21
|
+
import { UserSessionController } from './user.session.controller.ts';
|
|
22
|
+
import { SessionUserMiddleware } from './session-user.middleware.ts';
|
|
23
|
+
<%_ } _%>
|
|
24
|
+
<%_ if (authenticationUsesCsrf) { _%>
|
|
25
|
+
import { CsrfMiddleware } from './csrf.middleware.ts';
|
|
26
|
+
import { Session } from './session.entity.ts';
|
|
27
|
+
import { SessionStore } from './session.store.ts';
|
|
28
|
+
<%_ } _%>
|
|
29
|
+
<%_ if (authenticationType === 'jwt' && generateBuiltInUserEntity) { _%>
|
|
30
|
+
import { ConfigService } from '@nestjs/config';
|
|
31
|
+
import { JWT_ALGORITHM, jwtSecretKey } from './jwt.config.ts';
|
|
32
|
+
<%_ } _%>
|
|
33
|
+
<%_ if (generateBuiltInAuthorityEntity || authenticationUsesCsrf) { _%>
|
|
34
|
+
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
35
|
+
<%_ } _%>
|
|
36
|
+
<%_ if (generateBuiltInAuthorityEntity) { _%>
|
|
37
|
+
import { Authority } from '../users/authority.entity.ts';
|
|
38
|
+
import { AuthorityController } from '../users/authority.controller.ts';
|
|
39
|
+
<%_ } _%>
|
|
40
|
+
<%_ if (generateBuiltInUserEntity) { _%>
|
|
41
|
+
import { PublicUserController } from '../users/public.user.controller.ts';
|
|
42
|
+
<%_ } else { _%>
|
|
43
|
+
import { ManagementController } from '../management/management.controller.ts';
|
|
44
|
+
<%_ } _%>
|
|
45
|
+
import { AccountController } from './account.controller.ts';
|
|
46
|
+
|
|
47
|
+
@Module({
|
|
48
|
+
imports: [
|
|
49
|
+
<%_ if (generateBuiltInAuthorityEntity || authenticationUsesCsrf) { _%>
|
|
50
|
+
TypeOrmModule.forFeature([<%- [generateBuiltInAuthorityEntity && 'Authority', authenticationUsesCsrf && 'Session'].filter(Boolean).join(', ') %>]),
|
|
51
|
+
<%_ } _%>
|
|
52
|
+
<%_ if (generateBuiltInUserEntity) { _%>
|
|
53
|
+
UserModule,
|
|
54
|
+
<%_ } _%>
|
|
55
|
+
PassportModule,
|
|
56
|
+
<%_ if (authenticationType === 'jwt' && generateBuiltInUserEntity) { _%>
|
|
57
|
+
JwtModule.registerAsync({
|
|
58
|
+
inject: [ConfigService],
|
|
59
|
+
useFactory: (configService: ConfigService) => ({
|
|
60
|
+
secret: jwtSecretKey(configService),
|
|
61
|
+
signOptions: {
|
|
62
|
+
algorithm: JWT_ALGORITHM,
|
|
63
|
+
expiresIn: Number(configService.getOrThrow('jhipster.security.authentication.jwt.token-validity-in-seconds')),
|
|
64
|
+
},
|
|
65
|
+
}),
|
|
66
|
+
}),
|
|
67
|
+
<%_ } else if (authenticationType === 'oauth2') { _%>
|
|
68
|
+
HttpModule,
|
|
69
|
+
<%_ } _%>
|
|
70
|
+
],
|
|
71
|
+
controllers: [
|
|
72
|
+
<%_ if (authenticationType === 'jwt' && generateBuiltInUserEntity) { _%>
|
|
73
|
+
UserJWTController,
|
|
74
|
+
<%_ } else if (authenticationType === 'oauth2') { _%>
|
|
75
|
+
UserOauth2Controller,
|
|
76
|
+
<%_ } else if (authenticationTypeSession) { _%>
|
|
77
|
+
UserSessionController,
|
|
78
|
+
<%_ } _%>
|
|
79
|
+
<%_ if (generateBuiltInAuthorityEntity) { _%>
|
|
80
|
+
AuthorityController,
|
|
81
|
+
<%_ } _%>
|
|
82
|
+
<%- generateBuiltInUserEntity ? 'PublicUserController' : 'ManagementController' %>, AccountController ],
|
|
83
|
+
providers: [AuthService,
|
|
84
|
+
<%_ if (authenticationUsesCsrf) { _%>
|
|
85
|
+
SessionStore,
|
|
86
|
+
<%_ } _%>
|
|
87
|
+
<%_ if (authenticationType === 'jwt') { _%>
|
|
88
|
+
JwtStrategy
|
|
89
|
+
<%_ } else if (authenticationType === 'oauth2') { _%>
|
|
90
|
+
Oauth2Strategy,
|
|
91
|
+
Oauth2BearerStrategy
|
|
92
|
+
<%_ } _%>
|
|
93
|
+
],
|
|
94
|
+
exports: [AuthService],
|
|
95
|
+
})
|
|
96
|
+
<%_ if (authenticationUsesCsrf) { _%>
|
|
97
|
+
export class AuthModule implements NestModule {
|
|
98
|
+
configure(consumer: MiddlewareConsumer): void {
|
|
99
|
+
consumer.apply(<% if (authenticationTypeSession) { %>SessionUserMiddleware, <% } %>CsrfMiddleware).forRoutes('*');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
<%_ } else { _%>
|
|
103
|
+
export class AuthModule {}
|
|
104
|
+
<%_ } _%>
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { Injectable, <%_ if (generateUserManagement) { _%> HttpException, HttpStatus, InternalServerErrorException, <%_ } _%> Logger } from '@nestjs/common';
|
|
2
|
+
<%_ if (generateUserManagement) { _%>
|
|
3
|
+
import { randomBytes } from 'crypto';
|
|
4
|
+
<%_ } _%>
|
|
5
|
+
<%_ if (generateBuiltInAuthorityEntity) { _%>
|
|
6
|
+
import { InjectRepository } from '@nestjs/typeorm';
|
|
7
|
+
import { Repository } from 'typeorm';
|
|
8
|
+
<%_ } _%>
|
|
9
|
+
<%_ if (generateUserManagement) { _%>
|
|
10
|
+
import { PasswordService } from './password.service.ts';
|
|
11
|
+
<%_ } _%>
|
|
12
|
+
<%_ if (authenticationType === 'jwt' && generateBuiltInUserEntity) { _%>
|
|
13
|
+
import { JwtService } from '@nestjs/jwt';
|
|
14
|
+
import { UserLoginDTO } from './dto/user-login.dto.ts';
|
|
15
|
+
import { Payload } from './payload.interface.ts';
|
|
16
|
+
<%_ } _%>
|
|
17
|
+
<%_ if (generateBuiltInAuthorityEntity) { _%>
|
|
18
|
+
import { Authority } from '../users/authority.entity.ts';
|
|
19
|
+
<%_ } _%>
|
|
20
|
+
<%_ if (generateBuiltInUserEntity) { _%>
|
|
21
|
+
import { UserService } from '../users/user.service.ts';
|
|
22
|
+
<%_ if (generateUserManagement) { _%>
|
|
23
|
+
import { EmailAlreadyUsedException, InvalidPasswordException, LoginAlreadyUsedException } from '../common/errors/problem.exception.ts';
|
|
24
|
+
<%_ } _%>
|
|
25
|
+
<%_ } _%>
|
|
26
|
+
import { <%= userLoginVmClass %> } from '../users/dto/user.dto.ts';
|
|
27
|
+
<%_ if (authenticationType === 'jwt' && generateBuiltInUserEntity) { _%>
|
|
28
|
+
import { ConfigService } from '@nestjs/config';
|
|
29
|
+
<%_ } _%>
|
|
30
|
+
|
|
31
|
+
<%_
|
|
32
|
+
const userIdType = user?.primaryKey.tsType;
|
|
33
|
+
_%>
|
|
34
|
+
<%_ if (generateUserManagement) { _%>
|
|
35
|
+
|
|
36
|
+
/** The length of a password, like the one of the ManagedUserVM of JHipster Spring Boot. */
|
|
37
|
+
const PASSWORD_MIN_LENGTH = 4;
|
|
38
|
+
const PASSWORD_MAX_LENGTH = 100;
|
|
39
|
+
|
|
40
|
+
function isPasswordLengthInvalid(password?: string): boolean {
|
|
41
|
+
return !password || password.length < PASSWORD_MIN_LENGTH || password.length > PASSWORD_MAX_LENGTH;
|
|
42
|
+
}
|
|
43
|
+
<%_ } _%>
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@Injectable()
|
|
47
|
+
export class AuthService {
|
|
48
|
+
logger = new Logger('AuthService');
|
|
49
|
+
constructor(
|
|
50
|
+
<%_ if (authenticationType === 'jwt' && generateBuiltInUserEntity) { _%>
|
|
51
|
+
private readonly jwtService: JwtService,
|
|
52
|
+
private readonly configService: ConfigService,
|
|
53
|
+
<%_ } _%>
|
|
54
|
+
<%_ if (generateUserManagement) { _%>
|
|
55
|
+
private readonly passwordService: PasswordService,
|
|
56
|
+
<%_ } _%>
|
|
57
|
+
<%_ if (generateBuiltInAuthorityEntity) { _%>
|
|
58
|
+
@InjectRepository(Authority) private authorityRepository: Repository<Authority>,
|
|
59
|
+
<%_ } _%>
|
|
60
|
+
<%_ if (generateBuiltInUserEntity) { _%>
|
|
61
|
+
private userService: UserService,
|
|
62
|
+
<%_ } _%>
|
|
63
|
+
) {}
|
|
64
|
+
|
|
65
|
+
<%_ if (generateUserManagement) { _%>
|
|
66
|
+
/** The hash an unknown login is checked with, like the timing attack protection of the DaoAuthenticationProvider of Spring Security. */
|
|
67
|
+
private unknownUserPasswordHash?: Promise<string>;
|
|
68
|
+
|
|
69
|
+
/** The activated user of a login and its password, with its authorities, else 401, like the login of JHipster Spring Boot. */
|
|
70
|
+
async authenticate(loginUserName: string, loginPassword: string): Promise<<%= user.dtoClass %>> {
|
|
71
|
+
// The logins are in lower case, like the ones of JHipster Spring Boot.
|
|
72
|
+
const userFind = await this.userService.findByFields({ where: { login: loginUserName.toLowerCase() } });
|
|
73
|
+
// An unknown login checks the password with a hash too: it answers in the time of a known one, which does not tell
|
|
74
|
+
// the logins of the users.
|
|
75
|
+
this.unknownUserPasswordHash ??= this.passwordService.encode(randomBytes(16).toString('hex'));
|
|
76
|
+
const validPassword = await this.passwordService.matches(loginPassword, userFind?.password ?? (await this.unknownUserPasswordHash));
|
|
77
|
+
if (!userFind || !validPassword) {
|
|
78
|
+
throw new HttpException('Invalid login name or password!', HttpStatus.UNAUTHORIZED);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (userFind && !userFind.activated) {
|
|
82
|
+
throw new HttpException('Your account is not been activated!', HttpStatus.UNAUTHORIZED);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const user = await this.findUserWithAuthById(userFind.id!);
|
|
86
|
+
if (!user) {
|
|
87
|
+
throw new HttpException('Invalid login name or password!', HttpStatus.UNAUTHORIZED);
|
|
88
|
+
}
|
|
89
|
+
return user;
|
|
90
|
+
}
|
|
91
|
+
<%_ if (authenticationTypeJwt) { _%>
|
|
92
|
+
|
|
93
|
+
async login(userLogin: UserLoginDTO): Promise<any> {
|
|
94
|
+
const loginUserName = userLogin.username;
|
|
95
|
+
const loginPassword = userLogin.password;
|
|
96
|
+
if (!loginUserName || !loginPassword) {
|
|
97
|
+
throw new HttpException('Username and password are required!', HttpStatus.BAD_REQUEST);
|
|
98
|
+
}
|
|
99
|
+
const user = await this.authenticate(loginUserName, loginPassword);
|
|
100
|
+
|
|
101
|
+
const payload: Payload = { sub: user.login, auth: (user.authorities ?? []).join(' '), userId: user.id };
|
|
102
|
+
|
|
103
|
+
const expiresIn = Number(
|
|
104
|
+
userLogin.rememberMe
|
|
105
|
+
? this.configService.getOrThrow('jhipster.security.authentication.jwt.token-validity-in-seconds-for-remember-me')
|
|
106
|
+
: this.configService.getOrThrow('jhipster.security.authentication.jwt.token-validity-in-seconds'),
|
|
107
|
+
);
|
|
108
|
+
return {
|
|
109
|
+
id_token: this.jwtService.sign(payload, { expiresIn }),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async validateUser(payload: Payload): Promise<<%= user.dtoClass %> | undefined> {
|
|
114
|
+
// The user of the token subject, like the login of JHipster Spring Boot tokens.
|
|
115
|
+
return await this.userService.findByFields({ where: { login: payload.sub } });
|
|
116
|
+
}
|
|
117
|
+
<%_ } _%>
|
|
118
|
+
|
|
119
|
+
async findUserWithAuthById(userId: <%= userIdType %>): Promise<<%= user.dtoClass %> | undefined> {
|
|
120
|
+
<%_ if (databaseType === 'mongodb') { _%>
|
|
121
|
+
const userDTO = await this.userService.findById(userId);
|
|
122
|
+
<%_ } else { _%>
|
|
123
|
+
const userDTO = await this.userService.findByFields({ where: { id: userId } });
|
|
124
|
+
<%_ } _%>
|
|
125
|
+
return userDTO;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async getAccount(userId: <%= userIdType %>): Promise<<%= user.dtoClass %> | undefined> {
|
|
129
|
+
const userDTO = await this.findUserWithAuthById(userId);
|
|
130
|
+
if (!userDTO) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
return userDTO;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async changePassword(userLogin: string, currentClearTextPassword: string, newPassword: string): Promise<void> {
|
|
137
|
+
// Like the AccountResource of JHipster Spring Boot, the length of the new password.
|
|
138
|
+
if (isPasswordLengthInvalid(newPassword)) {
|
|
139
|
+
throw new InvalidPasswordException();
|
|
140
|
+
}
|
|
141
|
+
const userFind = await this.userService.findByFields({ where: { login: userLogin } });
|
|
142
|
+
if (!userFind) {
|
|
143
|
+
throw new HttpException('Invalid login name!', HttpStatus.BAD_REQUEST);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!(await this.passwordService.matches(currentClearTextPassword, userFind.password))) {
|
|
147
|
+
throw new InvalidPasswordException();
|
|
148
|
+
}
|
|
149
|
+
userFind.password = newPassword;
|
|
150
|
+
await this.userService.save(userFind, userLogin, true);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Like JHipster Spring Boot, gives a reset key, valid one day, to the activated user of the mail: the user returned
|
|
156
|
+
* is the one the mail with the key is for, undefined for another mail.
|
|
157
|
+
*/
|
|
158
|
+
async requestPasswordReset(mail: string): Promise<<%= user.dtoClass %> | undefined> {
|
|
159
|
+
const userFind = await this.userService.findByFields({ where: { email: mail.trim().toLowerCase() } });
|
|
160
|
+
if (!userFind?.activated) {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
userFind.resetKey = randomBytes(15).toString('base64url');
|
|
164
|
+
userFind.resetDate = new Date();
|
|
165
|
+
return this.userService.save(userFind, userFind.login);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Like JHipster Spring Boot, sets the new password of the user of a reset key given less than one day ago. */
|
|
169
|
+
async completePasswordReset(newPassword: string, key: string): Promise<void> {
|
|
170
|
+
const userFind = await this.userService.findByFields({ where: { resetKey: key } });
|
|
171
|
+
if (!userFind?.resetDate || new Date(userFind.resetDate).getTime() < Date.now() - 24 * 60 * 60 * 1000) {
|
|
172
|
+
throw new InternalServerErrorException('No user was found for this reset key');
|
|
173
|
+
}
|
|
174
|
+
userFind.password = newPassword;
|
|
175
|
+
// null clears the columns (TypeORM leaves the undefined properties unchanged).
|
|
176
|
+
Object.assign(userFind, { resetKey: null, resetDate: null });
|
|
177
|
+
await this.userService.save(userFind, userFind.login, true);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Like JHipster Spring Boot, registers a user not activated, with the fields of the registration only, its login and
|
|
182
|
+
* mail in lower case, and an activation key (sent by mail): the not activated user of the same login or mail, if any,
|
|
183
|
+
* is removed first.
|
|
184
|
+
*/
|
|
185
|
+
async registerNewUser(newUser: <%= user.dtoClass %>): Promise<<%= user.dtoClass %>> {
|
|
186
|
+
if (isPasswordLengthInvalid(newUser.password)) {
|
|
187
|
+
throw new InvalidPasswordException();
|
|
188
|
+
}
|
|
189
|
+
const login = newUser.login.toLowerCase();
|
|
190
|
+
const email = newUser.email.toLowerCase();
|
|
191
|
+
const userOfLogin = await this.userService.findByFields({ where: { login } });
|
|
192
|
+
if (userOfLogin && !(await this.removeNonActivatedUser(userOfLogin))) {
|
|
193
|
+
throw new LoginAlreadyUsedException();
|
|
194
|
+
}
|
|
195
|
+
const userOfEmail = await this.userService.findByFields({ where: { email } });
|
|
196
|
+
if (userOfEmail && !(await this.removeNonActivatedUser(userOfEmail))) {
|
|
197
|
+
throw new EmailAlreadyUsedException();
|
|
198
|
+
}
|
|
199
|
+
const user: <%= user.dtoClass %> = {
|
|
200
|
+
login,
|
|
201
|
+
password: newUser.password,
|
|
202
|
+
firstName: newUser.firstName,
|
|
203
|
+
lastName: newUser.lastName,
|
|
204
|
+
email,
|
|
205
|
+
imageUrl: newUser.imageUrl,
|
|
206
|
+
langKey: newUser.langKey,
|
|
207
|
+
// new user is not active
|
|
208
|
+
activated: false,
|
|
209
|
+
// new user gets registration key
|
|
210
|
+
activationKey: randomBytes(15).toString('base64url'),
|
|
211
|
+
authorities: ['ROLE_USER'],
|
|
212
|
+
};
|
|
213
|
+
return await this.userService.save(user, login, true);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Like JHipster Spring Boot, activates the user of an activation key, undefined for an unknown key. */
|
|
217
|
+
async activateRegistration(key: string): Promise<<%= user.dtoClass %> | undefined> {
|
|
218
|
+
const userFind = key ? await this.userService.findByFields({ where: { activationKey: key } }) : undefined;
|
|
219
|
+
if (!userFind) {
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
userFind.activated = true;
|
|
223
|
+
// null clears the column (TypeORM leaves the undefined properties unchanged).
|
|
224
|
+
Object.assign(userFind, { activationKey: null });
|
|
225
|
+
return await this.userService.save(userFind, userFind.login);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private async removeNonActivatedUser(existingUser: <%= user.dtoClass %>): Promise<boolean> {
|
|
229
|
+
if (existingUser.activated) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
await this.userService.delete(existingUser);
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async updateUserSettings(userLogin: string, newUserInfo: <%= user.dtoClass %>) : Promise<<%= user.dtoClass %>> {
|
|
237
|
+
const userFind = await this.userService.findByFields({ where: { login: userLogin } });
|
|
238
|
+
if (!userFind) {
|
|
239
|
+
throw new HttpException('Invalid login name!', HttpStatus.BAD_REQUEST);
|
|
240
|
+
}
|
|
241
|
+
// Like JHipster Spring Boot, the mail in lower case, of no other user.
|
|
242
|
+
const email = newUserInfo.email.toLowerCase();
|
|
243
|
+
const userFindEmail = await this.userService.findByFields({ where: { email } });
|
|
244
|
+
if (userFindEmail && userFindEmail.login !== userFind.login) {
|
|
245
|
+
throw new EmailAlreadyUsedException();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
userFind.firstName = newUserInfo.firstName;
|
|
249
|
+
userFind.lastName = newUserInfo.lastName;
|
|
250
|
+
userFind.email = email;
|
|
251
|
+
userFind.langKey = newUserInfo.langKey;
|
|
252
|
+
await this.userService.save(userFind, userLogin);
|
|
253
|
+
return userFind;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
<%_ } else if (authenticationType === 'oauth2') { _%>
|
|
257
|
+
<%_ if (generateBuiltInUserEntity) { _%>
|
|
258
|
+
/**
|
|
259
|
+
* The user of the identity provider in the database, like the user synchronization of JHipster Spring Boot: the
|
|
260
|
+
* authorities the database misses saved first, then the user created, or updated with the names, the mail, the
|
|
261
|
+
* language and the authorities of the identity provider.
|
|
262
|
+
*/
|
|
263
|
+
async findUserOrSave(loginUser: <%= userLoginVmClass %>): Promise<<%= userLoginVmClass %> | undefined> {
|
|
264
|
+
const authorities = loginUser.authorities ?? [];
|
|
265
|
+
<%_ if (generateBuiltInAuthorityEntity) { _%>
|
|
266
|
+
for (const name of authorities) {
|
|
267
|
+
if (!(await this.authorityRepository.findOne({ where: { name } }))) {
|
|
268
|
+
await this.authorityRepository.save(this.authorityRepository.create({ name }));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
<%_ } _%>
|
|
272
|
+
const userFound = await this.userService.findByFields({ where: { login: loginUser.login } });
|
|
273
|
+
const email = loginUser.email ?? userFound?.email ?? `${loginUser.login}@localhost`;
|
|
274
|
+
// The values the identity provider gives, over the ones of the user found: not the undefined properties of the user of
|
|
275
|
+
// the claims (its id among them), which would make another user.
|
|
276
|
+
const claims = Object.fromEntries(Object.entries(loginUser).filter(([, value]) => value !== undefined));
|
|
277
|
+
// Activated, like the users of JHipster Spring Boot synchronized from the identity provider, without a password.
|
|
278
|
+
await this.userService.save({ ...userFound, ...claims, email, activated: true, authorities } as <%= userLoginVmClass %>, loginUser.login);
|
|
279
|
+
return loginUser;
|
|
280
|
+
}
|
|
281
|
+
<%_ } _%>
|
|
282
|
+
|
|
283
|
+
getAccount(userDTO: <%= userLoginVmClass %>): any {
|
|
284
|
+
if (!userDTO) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
// Without the ID token of the session, kept for the logout only.
|
|
288
|
+
const { idToken: _idToken, ...account } = userDTO as any;
|
|
289
|
+
return account;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
<%_ } _%>
|
|
293
|
+
|
|
294
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { ForbiddenException } from '@nestjs/common';
|
|
3
|
+
import { <% if (authenticationTypeSession) { %>checkLoginCsrfToken, <% } %>CsrfMiddleware } from './csrf.middleware.ts';
|
|
4
|
+
|
|
5
|
+
const call = (method: string, session: any, headers: Record<string, string> = {}): { res: any; next: any } => {
|
|
6
|
+
const res = { cookie: vi.fn() };
|
|
7
|
+
const next = vi.fn();
|
|
8
|
+
new CsrfMiddleware().use({ method, headers, session } as any, res as any, next);
|
|
9
|
+
return { res, next };
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
describe('CsrfMiddleware', () => {
|
|
13
|
+
<%_ if (authenticationTypeSession) { _%>
|
|
14
|
+
it('lets the requests without a logged session pass, giving a token cookie to a client without one', () => {
|
|
15
|
+
const { res, next } = call('POST', {});
|
|
16
|
+
expect(next).toHaveBeenCalled();
|
|
17
|
+
expect(res.cookie).toHaveBeenCalledWith('XSRF-TOKEN', expect.any(String), expect.objectContaining({ path: '/' }));
|
|
18
|
+
expect(call('POST', {}, { cookie: 'XSRF-TOKEN=t0k' }).res.cookie).not.toHaveBeenCalled();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('refuses a login without the token of its cookie, like Spring Security', () => {
|
|
22
|
+
expect(() => checkLoginCsrfToken({ headers: {} } as any)).toThrow(ForbiddenException);
|
|
23
|
+
expect(() => checkLoginCsrfToken({ headers: { 'x-xsrf-token': 't0k' } } as any)).toThrow(ForbiddenException);
|
|
24
|
+
expect(() => checkLoginCsrfToken({ headers: { cookie: 'XSRF-TOKEN=t0k', 'x-xsrf-token': 'other' } } as any)).toThrow(ForbiddenException);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('accepts a login with the token of its cookie', () => {
|
|
28
|
+
expect(() => checkLoginCsrfToken({ headers: { cookie: 'SESSION=1; XSRF-TOKEN=t0k', 'x-xsrf-token': 't0k' } } as any)).not.toThrow();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('gives the CSRF cookie of a remember-me session its lifetime', () => {
|
|
32
|
+
const { res } = call('GET', { user: { login: 'user' }, csrfToken: 't0k', cookie: { originalMaxAge: 1000 } });
|
|
33
|
+
expect(res.cookie).toHaveBeenCalledWith('XSRF-TOKEN', 't0k', expect.objectContaining({ maxAge: 1000 }));
|
|
34
|
+
});
|
|
35
|
+
<%_ } else { _%>
|
|
36
|
+
it('lets the requests without a logged session pass, without the cookie', () => {
|
|
37
|
+
const { res, next } = call('POST', {});
|
|
38
|
+
expect(next).toHaveBeenCalled();
|
|
39
|
+
expect(res.cookie).not.toHaveBeenCalled();
|
|
40
|
+
});
|
|
41
|
+
<%_ } _%>
|
|
42
|
+
|
|
43
|
+
it('gives the token of a logged session back as the XSRF-TOKEN cookie on its safe requests too', () => {
|
|
44
|
+
const { res, next } = call('GET', { user: { login: 'user' }, csrfToken: 't0k' });
|
|
45
|
+
expect(next).toHaveBeenCalled();
|
|
46
|
+
expect(res.cookie).toHaveBeenCalledWith('XSRF-TOKEN', 't0k', expect.objectContaining({ path: '/', maxAge: expect.any(Number) }));
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('refuses an unsafe request of a logged session without its token, the logout too, like Spring Security', () => {
|
|
50
|
+
expect(() => call('POST', { user: { login: 'user' }, csrfToken: 't0k' })).toThrow(ForbiddenException);
|
|
51
|
+
expect(() => call('POST', { user: { login: 'user' }, csrfToken: 't0k' }, { 'x-xsrf-token': 'other' })).toThrow(ForbiddenException);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('accepts an unsafe request of a logged session with its token', () => {
|
|
55
|
+
const { next } = call('DELETE', { user: { login: 'user' }, csrfToken: 't0k' }, { 'x-xsrf-token': 't0k' });
|
|
56
|
+
expect(next).toHaveBeenCalled();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('creates the token of a session without one', () => {
|
|
60
|
+
const session: any = { user: { login: 'user' } };
|
|
61
|
+
const { res } = call('GET', session);
|
|
62
|
+
expect(session.csrfToken).toEqual(expect.any(String));
|
|
63
|
+
expect(res.cookie).toHaveBeenCalledWith('XSRF-TOKEN', session.csrfToken, expect.any(Object));
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import { ForbiddenException, Injectable, NestMiddleware } from '@nestjs/common';
|
|
3
|
+
import { NextFunction, Request, Response } from 'express';
|
|
4
|
+
|
|
5
|
+
/** The idle lifetime of a session, rolling: its cookie and the CSRF cookie expire together. */
|
|
6
|
+
export const SESSION_MAX_AGE = 30 * 60 * 1000;
|
|
7
|
+
<%_ if (authenticationTypeSession) { _%>
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The idle lifetime of a session logged in with remember-me, like the validity of the remember-me tokens of JHipster
|
|
11
|
+
* Spring Boot (31 days): kept in the database (SessionStore), the session outlives a restart like those tokens.
|
|
12
|
+
*/
|
|
13
|
+
export const REMEMBER_ME_MAX_AGE = 31 * 24 * 60 * 60 * 1000;
|
|
14
|
+
<%_ } _%>
|
|
15
|
+
|
|
16
|
+
/** The methods that change nothing, which the CSRF token does not protect, like the ones of Spring Security. */
|
|
17
|
+
const SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS', 'TRACE'];
|
|
18
|
+
|
|
19
|
+
/** The XSRF-TOKEN cookie the clients (Angular HttpClient, axios) send back as the X-XSRF-TOKEN header. */
|
|
20
|
+
export function setCsrfCookie(res: Response, token: string, maxAge = SESSION_MAX_AGE): void {
|
|
21
|
+
res.cookie('XSRF-TOKEN', token, { sameSite: 'lax', path: '/', maxAge });
|
|
22
|
+
}
|
|
23
|
+
<%_ if (authenticationTypeSession) { _%>
|
|
24
|
+
|
|
25
|
+
/** The XSRF-TOKEN cookie of a request. */
|
|
26
|
+
function csrfCookie(req: Request): string | undefined {
|
|
27
|
+
const cookie = req.headers.cookie
|
|
28
|
+
?.split(';')
|
|
29
|
+
.map(value => value.trim())
|
|
30
|
+
.find(value => value.startsWith('XSRF-TOKEN='));
|
|
31
|
+
return cookie ? decodeURIComponent(cookie.slice('XSRF-TOKEN='.length)) : undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The CSRF check of the session login, like the CookieCsrfTokenRepository of Spring Security: the X-XSRF-TOKEN header is
|
|
36
|
+
* the token of the XSRF-TOKEN cookie of the client, else 403, so another site cannot log the browser of a user into another
|
|
37
|
+
* account. Called by the login itself, whatever the path Express routes to it (its case, a trailing slash).
|
|
38
|
+
*/
|
|
39
|
+
export function checkLoginCsrfToken(req: Request): void {
|
|
40
|
+
const token = csrfCookie(req);
|
|
41
|
+
if (!token || req.headers['x-xsrf-token'] !== token) {
|
|
42
|
+
throw new ForbiddenException('Invalid CSRF token');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
<%_ } _%>
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The CSRF protection of the sessions, like the CSRF filter of Spring Security and the CookieCsrfFilter of JHipster Spring
|
|
49
|
+
* Boot: every response of a logged session gives its token back as the XSRF-TOKEN cookie, and every unsafe request of it,
|
|
50
|
+
* the logout included, must send it as X-XSRF-TOKEN, else 403. A request with a bearer token has no session to protect.
|
|
51
|
+
<%_ if (authenticationTypeSession) { _%>
|
|
52
|
+
* A client without a logged session gets a token cookie, which its login must send back (checkLoginCsrfToken).
|
|
53
|
+
<%_ } _%>
|
|
54
|
+
*/
|
|
55
|
+
@Injectable()
|
|
56
|
+
export class CsrfMiddleware implements NestMiddleware {
|
|
57
|
+
use(req: Request, res: Response, next: NextFunction): void {
|
|
58
|
+
const session = req.session as any;
|
|
59
|
+
if (!session?.user) {
|
|
60
|
+
<%_ if (authenticationTypeSession) { _%>
|
|
61
|
+
if (!csrfCookie(req)) {
|
|
62
|
+
setCsrfCookie(res, randomUUID());
|
|
63
|
+
}
|
|
64
|
+
<%_ } _%>
|
|
65
|
+
next();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
session.csrfToken ??= randomUUID();
|
|
69
|
+
// The CSRF cookie expires with the session cookie<% if (authenticationTypeSession) { %>, later for a remember-me login<% } %>.
|
|
70
|
+
setCsrfCookie(res, session.csrfToken, session.cookie?.originalMaxAge ?? SESSION_MAX_AGE);
|
|
71
|
+
if (!SAFE_METHODS.includes(req.method) && req.headers['x-xsrf-token'] !== session.csrfToken) {
|
|
72
|
+
throw new ForbiddenException('Invalid CSRF token');
|
|
73
|
+
}
|
|
74
|
+
next();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ApiProperty } from '@nestjs/swagger';
|
|
2
|
+
import { IsString, IsNotEmpty, Length } from 'class-validator';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The reset key and the new password of a password reset, like the KeyAndPasswordVM of JHipster Spring Boot.
|
|
6
|
+
*/
|
|
7
|
+
export class KeyAndPasswordDTO {
|
|
8
|
+
@ApiProperty({ description: 'Reset key' })
|
|
9
|
+
@IsString()
|
|
10
|
+
@IsNotEmpty()
|
|
11
|
+
readonly key!: string;
|
|
12
|
+
|
|
13
|
+
@ApiProperty({ description: 'New password' })
|
|
14
|
+
@IsString()
|
|
15
|
+
@Length(4, 100)
|
|
16
|
+
readonly newPassword!: string;
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ApiProperty } from '@nestjs/swagger';
|
|
2
|
+
import { IsString, IsNotEmpty } from 'class-validator';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A DTO representing a password change required data - current and new password.
|
|
6
|
+
*/
|
|
7
|
+
export class PasswordChangeDTO {
|
|
8
|
+
@ApiProperty({ description: 'Current password' })
|
|
9
|
+
@IsString()
|
|
10
|
+
@IsNotEmpty()
|
|
11
|
+
readonly currentPassword!: string;
|
|
12
|
+
|
|
13
|
+
@ApiProperty({ description: 'New password' })
|
|
14
|
+
@IsString()
|
|
15
|
+
@IsNotEmpty()
|
|
16
|
+
readonly newPassword!: string;
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ApiProperty } from '@nestjs/swagger';
|
|
2
|
+
import { Allow, IsString } from 'class-validator';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A DTO representing a login user.
|
|
6
|
+
*/
|
|
7
|
+
export class UserLoginDTO {
|
|
8
|
+
|
|
9
|
+
@ApiProperty({ description: 'User password' })
|
|
10
|
+
@IsString()
|
|
11
|
+
readonly password!: string;
|
|
12
|
+
|
|
13
|
+
@ApiProperty({ description: 'User remember login', required: false })
|
|
14
|
+
@Allow()
|
|
15
|
+
readonly rememberMe?: boolean;
|
|
16
|
+
|
|
17
|
+
@ApiProperty({ description: 'User login name' })
|
|
18
|
+
@IsString()
|
|
19
|
+
readonly username!: string;
|
|
20
|
+
|
|
21
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<%_ if (authenticationType === 'oauth2') { _%>
|
|
2
|
+
import { ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
|
|
3
|
+
import { AuthGuard as NestAuthGuard } from '@nestjs/passport';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The authentication of the API, like JHipster Spring Boot with OAuth2: the session of the login (the clients of the
|
|
7
|
+
* application, their unsafe requests checked by the CsrfMiddleware), else an access token of the identity provider as
|
|
8
|
+
* bearer token (oauth2ResourceServer), else 401.
|
|
9
|
+
*/
|
|
10
|
+
@Injectable()
|
|
11
|
+
export class AuthGuard extends NestAuthGuard('oauth2-bearer') {
|
|
12
|
+
canActivate(context: ExecutionContext): any {
|
|
13
|
+
const request = context.switchToHttp().getRequest();
|
|
14
|
+
|
|
15
|
+
if (request.session?.user) {
|
|
16
|
+
request.user = request.session.user;
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
// The authentication scheme is case insensitive, like HTTP gives it.
|
|
20
|
+
if (!/^bearer\s+\S/i.test(request.headers.authorization ?? '')) {
|
|
21
|
+
throw new UnauthorizedException();
|
|
22
|
+
}
|
|
23
|
+
return super.canActivate(context);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
<%_ } else if (authenticationTypeSession) { _%>
|
|
27
|
+
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The authentication of the API, like JHipster Spring Boot with sessions: the user of the session of the login (its unsafe
|
|
31
|
+
* requests checked by the CsrfMiddleware), else 401.
|
|
32
|
+
*/
|
|
33
|
+
@Injectable()
|
|
34
|
+
export class AuthGuard implements CanActivate {
|
|
35
|
+
canActivate(context: ExecutionContext): boolean {
|
|
36
|
+
const request = context.switchToHttp().getRequest();
|
|
37
|
+
if (!request.session?.user) {
|
|
38
|
+
throw new UnauthorizedException();
|
|
39
|
+
}
|
|
40
|
+
request.user = request.session.user;
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
<%_ } else { _%>
|
|
45
|
+
import { Injectable } from '@nestjs/common';
|
|
46
|
+
import { AuthGuard as NestAuthGuard } from '@nestjs/passport';
|
|
47
|
+
|
|
48
|
+
@Injectable()
|
|
49
|
+
export class AuthGuard extends NestAuthGuard('<%= authenticationType %>') {}
|
|
50
|
+
<%_ } _%>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { AuthGuard as NestAuthGuard } from '@nestjs/passport';
|
|
3
|
+
|
|
4
|
+
/** The login with the identity provider (authorization code with PKCE), on the login routes only. */
|
|
5
|
+
@Injectable()
|
|
6
|
+
export class OAuth2LoginGuard extends NestAuthGuard('oauth2') {}
|