From bb86a25ad40ab212a21636877ce78ab71b6c31cb Mon Sep 17 00:00:00 2001 From: Evert Prants Date: Sat, 27 Aug 2022 11:59:26 +0300 Subject: [PATCH] rearrange stuff --- package-lock.json | 225 +++++++++++------- package.json | 2 +- src/app.module.ts | 49 +--- src/decorators/bearer.decorator.ts | 13 + src/decorators/privileges.decorator.ts | 8 + src/decorators/scope.decorator.ts | 14 ++ src/decorators/scopes.decorator.ts | 7 + src/decorators/user.decorator.ts | 11 + src/guards/oauth2.guard.ts | 43 ++++ src/guards/privileges.guard.ts | 28 +++ src/guards/scopes.guard.ts | 26 ++ src/main.ts | 5 +- src/modules/api/admin/admin.module.ts | 4 + src/modules/api/api.controller.ts | 15 ++ src/modules/api/api.module.ts | 26 ++ src/modules/features/oauth2/oauth2.module.ts | 27 --- .../oauth2/adapter/access-token.adapter.ts | 0 .../oauth2/adapter/client.adapter.ts | 0 .../oauth2/adapter/code.adapter.ts | 0 .../oauth2/adapter/refresh-token.adapter.ts | 0 .../oauth2/adapter/user.adapter.ts | 0 src/modules/oauth2/oauth2.module.ts | 13 + .../{features => }/oauth2/oauth2.service.ts | 0 .../oauth2-client/oauth2-client.service.ts | 10 +- src/modules/objects/objects.module.ts | 36 +++ .../user-token/user-totp-token.service.ts | 2 +- .../login/login.controller.ts | 35 +-- .../login/login.module.ts | 0 .../oauth2-router.controller.ts} | 28 +-- .../oauth2-router/oauth2-router.module.ts | 22 ++ .../register/register.controller.ts | 0 .../register/register.interfaces.ts | 0 .../register/register.module.ts | 0 .../settings/settings.controller.ts | 0 .../settings/settings.module.ts | 2 +- .../settings/settings.service.ts | 2 +- .../static-front-end.module.ts | 46 ++++ .../two-factor/two-factor.controller.ts | 0 .../two-factor/two-factor.module.ts | 0 views/login/totp-verify.pug | 4 +- 40 files changed, 508 insertions(+), 195 deletions(-) create mode 100644 src/decorators/bearer.decorator.ts create mode 100644 src/decorators/privileges.decorator.ts create mode 100644 src/decorators/scope.decorator.ts create mode 100644 src/decorators/scopes.decorator.ts create mode 100644 src/decorators/user.decorator.ts create mode 100644 src/guards/oauth2.guard.ts create mode 100644 src/guards/privileges.guard.ts create mode 100644 src/guards/scopes.guard.ts create mode 100644 src/modules/api/admin/admin.module.ts create mode 100644 src/modules/api/api.controller.ts create mode 100644 src/modules/api/api.module.ts delete mode 100644 src/modules/features/oauth2/oauth2.module.ts rename src/modules/{features => }/oauth2/adapter/access-token.adapter.ts (100%) rename src/modules/{features => }/oauth2/adapter/client.adapter.ts (100%) rename src/modules/{features => }/oauth2/adapter/code.adapter.ts (100%) rename src/modules/{features => }/oauth2/adapter/refresh-token.adapter.ts (100%) rename src/modules/{features => }/oauth2/adapter/user.adapter.ts (100%) create mode 100644 src/modules/oauth2/oauth2.module.ts rename src/modules/{features => }/oauth2/oauth2.service.ts (100%) create mode 100644 src/modules/objects/objects.module.ts rename src/modules/{features => static-front-end}/login/login.controller.ts (90%) rename src/modules/{features => static-front-end}/login/login.module.ts (100%) rename src/modules/{features/oauth2/oauth2.controller.ts => static-front-end/oauth2-router/oauth2-router.controller.ts} (75%) create mode 100644 src/modules/static-front-end/oauth2-router/oauth2-router.module.ts rename src/modules/{features => static-front-end}/register/register.controller.ts (100%) rename src/modules/{features => static-front-end}/register/register.interfaces.ts (100%) rename src/modules/{features => static-front-end}/register/register.module.ts (100%) rename src/modules/{features => static-front-end}/settings/settings.controller.ts (100%) rename src/modules/{features => static-front-end}/settings/settings.module.ts (97%) rename src/modules/{features => static-front-end}/settings/settings.service.ts (83%) create mode 100644 src/modules/static-front-end/static-front-end.module.ts rename src/modules/{features => static-front-end}/two-factor/two-factor.controller.ts (100%) rename src/modules/{features => static-front-end}/two-factor/two-factor.module.ts (100%) diff --git a/package-lock.json b/package-lock.json index 62e3ded..ab859e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.1", "license": "UNLICENSED", "dependencies": { - "@icynet/oauth2-provider": "git+ssh://git@gitlab.icynet.eu:IcyNetwork/oauth2-provider.git", + "@icynet/oauth2-provider": "git+https://gitlab.icynet.eu/IcyNetwork/oauth2-provider.git", "@nestjs/common": "^9.0.11", "@nestjs/core": "^9.0.11", "@nestjs/platform-express": "^9.0.11", @@ -34,7 +34,7 @@ "otplib": "^12.0.1", "pug": "^3.0.2", "qrcode": "^1.5.1", - "redis": "^3.1.2", + "redis": "^4.3.0", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.5.6", @@ -2135,7 +2135,7 @@ }, "node_modules/@icynet/oauth2-provider": { "version": "1.0.0", - "resolved": "git+ssh://git@gitlab.icynet.eu:IcyNetwork/oauth2-provider.git#a440d1f4ac53ccb6989dd25221797490611e240a", + "resolved": "git+https://gitlab.icynet.eu/IcyNetwork/oauth2-provider.git#a440d1f4ac53ccb6989dd25221797490611e240a", "license": "MIT", "dependencies": { "express": "^4.17.3", @@ -3147,6 +3147,59 @@ "@otplib/plugin-thirty-two": "^12.0.1" } }, + "node_modules/@redis/bloom": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.0.2.tgz", + "integrity": "sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.3.0.tgz", + "integrity": "sha512-XCFV60nloXAefDsPnYMjHGtvbtHR8fV5Om8cQ0JYqTNbWcQo/4AryzJ2luRj4blveWazRK/j40gES8M7Cp6cfQ==", + "dependencies": { + "cluster-key-slot": "1.1.0", + "generic-pool": "3.8.2", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/graph": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.0.1.tgz", + "integrity": "sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.3.tgz", + "integrity": "sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.0.tgz", + "integrity": "sha512-NyFZEVnxIJEybpy+YskjgOJRNsfTYqaPbK/Buv6W2kmFNaRk85JiqjJZA5QkRmWvGbyQYwoO5QfDi2wHskKrQQ==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.3.tgz", + "integrity": "sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, "node_modules/@sinclair/typebox": { "version": "0.24.28", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.28.tgz", @@ -4997,6 +5050,14 @@ "node": ">=6" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", + "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -6713,6 +6774,14 @@ "is-property": "^1.0.2" } }, + "node_modules/generic-pool": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz", + "integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==", + "engines": { + "node": ">= 4" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -10122,53 +10191,16 @@ } }, "node_modules/redis": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz", - "integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.3.0.tgz", + "integrity": "sha512-RXRUor0iU1vizu4viHoUyLpe1ZO/RngZp0V9DyXBHTI+7tC7rEz6Wzn4Sv9v0tTJeqGAzdJ+q5YVbNKKQ5hX9A==", "dependencies": { - "denque": "^1.5.0", - "redis-commands": "^1.7.0", - "redis-errors": "^1.2.0", - "redis-parser": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-redis" - } - }, - "node_modules/redis-commands": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", - "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" - }, - "node_modules/redis-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", - "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", - "engines": { - "node": ">=4" - } - }, - "node_modules/redis-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", - "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", - "dependencies": { - "redis-errors": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/redis/node_modules/denque": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", - "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==", - "engines": { - "node": ">=0.10" + "@redis/bloom": "1.0.2", + "@redis/client": "1.3.0", + "@redis/graph": "1.0.1", + "@redis/json": "1.0.3", + "@redis/search": "1.1.0", + "@redis/time-series": "1.0.3" } }, "node_modules/reflect-metadata": { @@ -13918,8 +13950,8 @@ "dev": true }, "@icynet/oauth2-provider": { - "version": "git+ssh://git@gitlab.icynet.eu:IcyNetwork/oauth2-provider.git#a440d1f4ac53ccb6989dd25221797490611e240a", - "from": "@icynet/oauth2-provider@git+ssh://git@gitlab.icynet.eu:IcyNetwork/oauth2-provider.git", + "version": "git+https://gitlab.icynet.eu/IcyNetwork/oauth2-provider.git#a440d1f4ac53ccb6989dd25221797490611e240a", + "from": "@icynet/oauth2-provider@git+https://gitlab.icynet.eu/IcyNetwork/oauth2-provider.git", "requires": { "express": "^4.17.3", "express-session": "^1.17.2" @@ -14682,6 +14714,46 @@ "@otplib/plugin-thirty-two": "^12.0.1" } }, + "@redis/bloom": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.0.2.tgz", + "integrity": "sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==", + "requires": {} + }, + "@redis/client": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.3.0.tgz", + "integrity": "sha512-XCFV60nloXAefDsPnYMjHGtvbtHR8fV5Om8cQ0JYqTNbWcQo/4AryzJ2luRj4blveWazRK/j40gES8M7Cp6cfQ==", + "requires": { + "cluster-key-slot": "1.1.0", + "generic-pool": "3.8.2", + "yallist": "4.0.0" + } + }, + "@redis/graph": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.0.1.tgz", + "integrity": "sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==", + "requires": {} + }, + "@redis/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.3.tgz", + "integrity": "sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==", + "requires": {} + }, + "@redis/search": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.0.tgz", + "integrity": "sha512-NyFZEVnxIJEybpy+YskjgOJRNsfTYqaPbK/Buv6W2kmFNaRk85JiqjJZA5QkRmWvGbyQYwoO5QfDi2wHskKrQQ==", + "requires": {} + }, + "@redis/time-series": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.3.tgz", + "integrity": "sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==", + "requires": {} + }, "@sinclair/typebox": { "version": "0.24.28", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.28.tgz", @@ -16157,6 +16229,11 @@ "shallow-clone": "^3.0.0" } }, + "cluster-key-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", + "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==" + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -17475,6 +17552,11 @@ "is-property": "^1.0.2" } }, + "generic-pool": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz", + "integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==" + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -20051,39 +20133,16 @@ } }, "redis": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz", - "integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.3.0.tgz", + "integrity": "sha512-RXRUor0iU1vizu4viHoUyLpe1ZO/RngZp0V9DyXBHTI+7tC7rEz6Wzn4Sv9v0tTJeqGAzdJ+q5YVbNKKQ5hX9A==", "requires": { - "denque": "^1.5.0", - "redis-commands": "^1.7.0", - "redis-errors": "^1.2.0", - "redis-parser": "^3.0.0" - }, - "dependencies": { - "denque": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", - "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==" - } - } - }, - "redis-commands": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", - "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" - }, - "redis-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", - "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==" - }, - "redis-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", - "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", - "requires": { - "redis-errors": "^1.0.0" + "@redis/bloom": "1.0.2", + "@redis/client": "1.3.0", + "@redis/graph": "1.0.1", + "@redis/json": "1.0.3", + "@redis/search": "1.1.0", + "@redis/time-series": "1.0.3" } }, "reflect-metadata": { diff --git a/package.json b/package.json index dc9ab96..634b9af 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "otplib": "^12.0.1", "pug": "^3.0.2", "qrcode": "^1.5.1", - "redis": "^3.1.2", + "redis": "^4.3.0", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.5.6", diff --git a/src/app.module.ts b/src/app.module.ts index 711ce56..d0c1c78 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,30 +1,15 @@ -import { - MiddlewareConsumer, - Module, - NestModule, - RequestMethod, -} from '@nestjs/common'; +import { Module } from '@nestjs/common'; import { ServeStaticModule } from '@nestjs/serve-static'; import { ThrottlerModule } from '@nestjs/throttler'; import { join } from 'path'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { CSRFMiddleware } from './middleware/csrf.middleware'; -import { UserMiddleware } from './middleware/user.middleware'; +import { ApiModule } from './modules/api/api.module'; import { ConfigurationModule } from './modules/config/config.module'; -import { LoginModule } from './modules/features/login/login.module'; -import { OAuth2Module } from './modules/features/oauth2/oauth2.module'; -import { RegisterModule } from './modules/features/register/register.module'; -import { SettingsModule } from './modules/features/settings/settings.module'; -import { TwoFactorModule } from './modules/features/two-factor/two-factor.module'; + import { JWTModule } from './modules/jwt/jwt.module'; -import { DatabaseModule } from './modules/objects/database/database.module'; -import { EmailModule } from './modules/objects/email/email.module'; -import { OAuth2ClientModule } from './modules/objects/oauth2-client/oauth2-client.module'; -import { OAuth2TokenModule } from './modules/objects/oauth2-token/oauth2-token.module'; -import { PrivilegeModule } from './modules/objects/privilege/privilege.module'; -import { UploadModule } from './modules/objects/upload/upload.module'; -import { UserModule } from './modules/objects/user/user.module'; +import { StaticFrontEndModule } from './modules/static-front-end/static-front-end.module'; import { UtilityModule } from './modules/utility/utility.module'; @Module({ @@ -39,31 +24,11 @@ import { UtilityModule } from './modules/utility/utility.module'; }), ConfigurationModule, UtilityModule, - DatabaseModule, - EmailModule, - UserModule, - UploadModule, - OAuth2ClientModule, - OAuth2TokenModule, - LoginModule, - RegisterModule, - OAuth2Module, JWTModule, - TwoFactorModule, - SettingsModule, - PrivilegeModule, + StaticFrontEndModule, + ApiModule, ], controllers: [AppController], providers: [AppService, CSRFMiddleware], }) -export class AppModule implements NestModule { - configure(consumer: MiddlewareConsumer) { - consumer - .apply(CSRFMiddleware, UserMiddleware) - .exclude( - { path: 'uploads*', method: RequestMethod.ALL }, - { path: 'public*', method: RequestMethod.ALL }, - ) - .forRoutes('*'); - } -} +export class AppModule {} diff --git a/src/decorators/bearer.decorator.ts b/src/decorators/bearer.decorator.ts new file mode 100644 index 0000000..e6a6332 --- /dev/null +++ b/src/decorators/bearer.decorator.ts @@ -0,0 +1,13 @@ +import { OAuth2AccessToken } from '@icynet/oauth2-provider'; +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +/** + * Get the OAuth2 bearer token from the response. + * Requires the OAuth2 guard or bearer middleware! + */ +export const Bearer = createParamDecorator( + (data: unknown, ctx: ExecutionContext) => { + const response = ctx.switchToHttp().getResponse(); + return response.locals.accessToken as OAuth2AccessToken; + }, +); diff --git a/src/decorators/privileges.decorator.ts b/src/decorators/privileges.decorator.ts new file mode 100644 index 0000000..f3b3cbf --- /dev/null +++ b/src/decorators/privileges.decorator.ts @@ -0,0 +1,8 @@ +import { SetMetadata } from '@nestjs/common'; + +/** + * Restrict this route to only these privileges. AND logic! + * @param privileges List of privileges for this route + */ +export const Privileges = (...privileges: string[]) => + SetMetadata('privileges', privileges); diff --git a/src/decorators/scope.decorator.ts b/src/decorators/scope.decorator.ts new file mode 100644 index 0000000..1ca4a7c --- /dev/null +++ b/src/decorators/scope.decorator.ts @@ -0,0 +1,14 @@ +import { OAuth2AccessToken } from '@icynet/oauth2-provider'; +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +/** + * Get the OAuth2 access token scope from the response. + * Requires the OAuth2 guard or bearer middleware! + */ +export const Scope = createParamDecorator( + (data: unknown, ctx: ExecutionContext) => { + const response = ctx.switchToHttp().getResponse(); + const token = response.locals.accessToken as OAuth2AccessToken; + return token.scope; + }, +); diff --git a/src/decorators/scopes.decorator.ts b/src/decorators/scopes.decorator.ts new file mode 100644 index 0000000..4c6bc5b --- /dev/null +++ b/src/decorators/scopes.decorator.ts @@ -0,0 +1,7 @@ +import { SetMetadata } from '@nestjs/common'; + +/** + * Restrict this route to only these OAuth2 scopes. AND logic! + * @param scopes List of scopes for this route + */ +export const Scopes = (...scopes: string[]) => SetMetadata('scopes', scopes); diff --git a/src/decorators/user.decorator.ts b/src/decorators/user.decorator.ts new file mode 100644 index 0000000..14dd42f --- /dev/null +++ b/src/decorators/user.decorator.ts @@ -0,0 +1,11 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +/** + * Get the User from the current request. Requires a guard or middleware to inject the user. + */ +export const CurrentUser = createParamDecorator( + (data: unknown, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest(); + return request.user; + }, +); diff --git a/src/guards/oauth2.guard.ts b/src/guards/oauth2.guard.ts new file mode 100644 index 0000000..6cd11af --- /dev/null +++ b/src/guards/oauth2.guard.ts @@ -0,0 +1,43 @@ +import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { OAuth2Service } from 'src/modules/oauth2/oauth2.service'; +import { UserService } from 'src/modules/objects/user/user.service'; + +/** + * Injects and validates OAuth2 bearer tokens. + */ +@Injectable() +export class OAuth2Guard implements CanActivate { + constructor(private _oauth2: OAuth2Service, private _user: UserService) {} + + canActivate( + context: ExecutionContext, + ): boolean | Promise | Observable { + const http = context.switchToHttp(); + const request = http.getRequest(); + const response = http.getResponse(); + + return new Promise((resolve, reject) => { + try { + this._oauth2.oauth.bearer(request, response, (content) => { + if (content instanceof Error) { + return reject(content); + } + + this._user + .getById(response.locals.accessToken.user_id, [ + 'picture', + 'privileges', + ]) + .then((user) => { + request.user = user; + resolve(true); + }) + .catch(reject); + }); + } catch (e: any) { + reject(e); + } + }); + } +} diff --git a/src/guards/privileges.guard.ts b/src/guards/privileges.guard.ts new file mode 100644 index 0000000..3535e23 --- /dev/null +++ b/src/guards/privileges.guard.ts @@ -0,0 +1,28 @@ +import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; + +/** + * Validates privileges. + */ +@Injectable() +export class PrivilegesGuard implements CanActivate { + constructor(private reflector: Reflector) {} + + canActivate(context: ExecutionContext): boolean { + const privileges = this.reflector.get( + 'privileges', + context.getHandler(), + ); + if (!privileges) { + return true; + } + const request = context.switchToHttp().getRequest(); + const user = request.user; + return ( + user.privileges.includes('*') || + privileges.every((item) => + user.privileges.find(({ name }) => name === item), + ) + ); + } +} diff --git a/src/guards/scopes.guard.ts b/src/guards/scopes.guard.ts new file mode 100644 index 0000000..c242c63 --- /dev/null +++ b/src/guards/scopes.guard.ts @@ -0,0 +1,26 @@ +import { OAuth2AccessToken } from '@icynet/oauth2-provider'; +import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; + +/** + * Validates OAuth2 scopes. + */ +@Injectable() +export class ScopesGuard implements CanActivate { + constructor(private reflector: Reflector) {} + + canActivate(context: ExecutionContext): boolean { + const scopes = this.reflector.get('scopes', context.getHandler()); + if (!scopes) { + return true; + } + + const response = context.switchToHttp().getResponse(); + const accessToken = response.locals.accessToken as OAuth2AccessToken; + if (!accessToken) { + return false; + } + + return scopes.every((scope) => accessToken.scope.includes(scope)); + } +} diff --git a/src/main.ts b/src/main.ts index b56b968..a2ca686 100644 --- a/src/main.ts +++ b/src/main.ts @@ -15,9 +15,10 @@ async function bootstrap() { const RedisStore = connectRedis(session); const redisClient = redis.createClient({ - host: 'localhost', - port: 6379, + url: process.env.REDIS_URL || 'redis://localhost:6379', + legacyMode: true, }); + redisClient.connect(); // app.use(express.urlencoded()); app.use(cookieParser()); diff --git a/src/modules/api/admin/admin.module.ts b/src/modules/api/admin/admin.module.ts new file mode 100644 index 0000000..56a8a3f --- /dev/null +++ b/src/modules/api/admin/admin.module.ts @@ -0,0 +1,4 @@ +import { Module } from '@nestjs/common'; + +@Module({}) +export class AdminApiModule {} diff --git a/src/modules/api/api.controller.ts b/src/modules/api/api.controller.ts new file mode 100644 index 0000000..6201ddc --- /dev/null +++ b/src/modules/api/api.controller.ts @@ -0,0 +1,15 @@ +import { Controller, Get, UseGuards } from '@nestjs/common'; +import { CurrentUser } from 'src/decorators/user.decorator'; +import { OAuth2Guard } from 'src/guards/oauth2.guard'; +import { User } from '../objects/user/user.entity'; + +@Controller({ + path: '/api', +}) +export class ApiController { + @Get('/') + @UseGuards(OAuth2Guard) + index(@CurrentUser() user: User) { + return { hello: true, user: user.username }; + } +} diff --git a/src/modules/api/api.module.ts b/src/modules/api/api.module.ts new file mode 100644 index 0000000..09e398b --- /dev/null +++ b/src/modules/api/api.module.ts @@ -0,0 +1,26 @@ +import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; +import { ConfigurationModule } from '../config/config.module'; +import { JWTModule } from '../jwt/jwt.module'; +import { OAuth2Module } from '../oauth2/oauth2.module'; +import { OAuth2Service } from '../oauth2/oauth2.service'; +import { ObjectsModule } from '../objects/objects.module'; +import { AdminApiModule } from './admin/admin.module'; +import { ApiController } from './api.controller'; + +@Module({ + controllers: [ApiController], + imports: [ + ConfigurationModule, + JWTModule, + ObjectsModule, + AdminApiModule, + OAuth2Module, + ], +}) +export class ApiModule implements NestModule { + constructor(private _service: OAuth2Service) {} + + configure(consumer: MiddlewareConsumer) { + consumer.apply(this._service.oauth.express()).forRoutes('/api*'); + } +} diff --git a/src/modules/features/oauth2/oauth2.module.ts b/src/modules/features/oauth2/oauth2.module.ts deleted file mode 100644 index 6324372..0000000 --- a/src/modules/features/oauth2/oauth2.module.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; -import { AuthMiddleware } from 'src/middleware/auth.middleware'; -import { ValidateCSRFMiddleware } from 'src/middleware/validate-csrf.middleware'; -import { OAuth2ClientModule } from 'src/modules/objects/oauth2-client/oauth2-client.module'; -import { OAuth2TokenModule } from 'src/modules/objects/oauth2-token/oauth2-token.module'; -import { UploadModule } from 'src/modules/objects/upload/upload.module'; -import { UserModule } from 'src/modules/objects/user/user.module'; -import { OAuth2Controller } from './oauth2.controller'; -import { OAuth2Service } from './oauth2.service'; - -@Module({ - imports: [UserModule, UploadModule, OAuth2ClientModule, OAuth2TokenModule], - controllers: [OAuth2Controller], - providers: [OAuth2Service], - exports: [OAuth2Service], -}) -export class OAuth2Module implements NestModule { - constructor(private _service: OAuth2Service) {} - - configure(consumer: MiddlewareConsumer) { - consumer.apply(this._service.oauth.express()).forRoutes('oauth2/*'); - consumer.apply(this._service.oauth.bearer).forRoutes('oauth2/user'); - consumer - .apply(AuthMiddleware, ValidateCSRFMiddleware) - .forRoutes('oauth2/authorize'); - } -} diff --git a/src/modules/features/oauth2/adapter/access-token.adapter.ts b/src/modules/oauth2/adapter/access-token.adapter.ts similarity index 100% rename from src/modules/features/oauth2/adapter/access-token.adapter.ts rename to src/modules/oauth2/adapter/access-token.adapter.ts diff --git a/src/modules/features/oauth2/adapter/client.adapter.ts b/src/modules/oauth2/adapter/client.adapter.ts similarity index 100% rename from src/modules/features/oauth2/adapter/client.adapter.ts rename to src/modules/oauth2/adapter/client.adapter.ts diff --git a/src/modules/features/oauth2/adapter/code.adapter.ts b/src/modules/oauth2/adapter/code.adapter.ts similarity index 100% rename from src/modules/features/oauth2/adapter/code.adapter.ts rename to src/modules/oauth2/adapter/code.adapter.ts diff --git a/src/modules/features/oauth2/adapter/refresh-token.adapter.ts b/src/modules/oauth2/adapter/refresh-token.adapter.ts similarity index 100% rename from src/modules/features/oauth2/adapter/refresh-token.adapter.ts rename to src/modules/oauth2/adapter/refresh-token.adapter.ts diff --git a/src/modules/features/oauth2/adapter/user.adapter.ts b/src/modules/oauth2/adapter/user.adapter.ts similarity index 100% rename from src/modules/features/oauth2/adapter/user.adapter.ts rename to src/modules/oauth2/adapter/user.adapter.ts diff --git a/src/modules/oauth2/oauth2.module.ts b/src/modules/oauth2/oauth2.module.ts new file mode 100644 index 0000000..3f0bca1 --- /dev/null +++ b/src/modules/oauth2/oauth2.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { OAuth2ClientModule } from 'src/modules/objects/oauth2-client/oauth2-client.module'; +import { OAuth2TokenModule } from 'src/modules/objects/oauth2-token/oauth2-token.module'; +import { UploadModule } from 'src/modules/objects/upload/upload.module'; +import { UserModule } from 'src/modules/objects/user/user.module'; +import { OAuth2Service } from './oauth2.service'; + +@Module({ + imports: [UserModule, UploadModule, OAuth2ClientModule, OAuth2TokenModule], + providers: [OAuth2Service], + exports: [OAuth2Service], +}) +export class OAuth2Module {} diff --git a/src/modules/features/oauth2/oauth2.service.ts b/src/modules/oauth2/oauth2.service.ts similarity index 100% rename from src/modules/features/oauth2/oauth2.service.ts rename to src/modules/oauth2/oauth2.service.ts diff --git a/src/modules/objects/oauth2-client/oauth2-client.service.ts b/src/modules/objects/oauth2-client/oauth2-client.service.ts index f0c9ed9..3bd4066 100644 --- a/src/modules/objects/oauth2-client/oauth2-client.service.ts +++ b/src/modules/objects/oauth2-client/oauth2-client.service.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { DeleteResult, Repository } from 'typeorm'; +import { Repository } from 'typeorm'; import { User } from '../user/user.entity'; import { OAuth2ClientAuthorization } from './oauth2-client-authorization.entity'; import { @@ -56,8 +56,8 @@ export class OAuth2ClientService { ): Promise { const existing = await this.clientAuthRepository.findOne({ where: { - user, - client, + user: { id: user.id }, + client: { id: client.id }, }, relations: ['user', 'client'], }); @@ -87,7 +87,7 @@ export class OAuth2ClientService { ): Promise { return this.clientAuthRepository.find({ relations: ['user', 'client', 'client.urls'], - where: { user }, + where: { user: { id: user.id } }, }); } @@ -104,7 +104,7 @@ export class OAuth2ClientService { ): Promise { return this.clientAuthRepository.findOne({ where: { - user, + user: { id: user.id }, id: authId, }, relations: ['user'], diff --git a/src/modules/objects/objects.module.ts b/src/modules/objects/objects.module.ts new file mode 100644 index 0000000..8ce78aa --- /dev/null +++ b/src/modules/objects/objects.module.ts @@ -0,0 +1,36 @@ +import { Module } from '@nestjs/common'; +import { ConfigurationModule } from '../config/config.module'; + +import { DatabaseModule } from './database/database.module'; +import { DocumentModule } from './document/document.module'; +import { EmailModule } from './email/email.module'; +import { OAuth2ClientModule } from './oauth2-client/oauth2-client.module'; +import { OAuth2TokenModule } from './oauth2-token/oauth2-token.module'; +import { PrivilegeModule } from './privilege/privilege.module'; +import { UploadModule } from './upload/upload.module'; +import { UserModule } from './user/user.module'; + +@Module({ + imports: [ + ConfigurationModule, + DatabaseModule, + EmailModule, + OAuth2ClientModule, + OAuth2TokenModule, + PrivilegeModule, + UploadModule, + UserModule, + DocumentModule, + ], + exports: [ + DatabaseModule, + EmailModule, + OAuth2ClientModule, + OAuth2TokenModule, + PrivilegeModule, + UploadModule, + UserModule, + DocumentModule, + ], +}) +export class ObjectsModule {} diff --git a/src/modules/objects/user-token/user-totp-token.service.ts b/src/modules/objects/user-token/user-totp-token.service.ts index 5403290..562be2d 100644 --- a/src/modules/objects/user-token/user-totp-token.service.ts +++ b/src/modules/objects/user-token/user-totp-token.service.ts @@ -33,7 +33,7 @@ export class UserTOTPService { */ public async getUserTOTP(user: User): Promise { return this.userTokenRepository.findOne({ - where: { user, type: UserTokenType.TOTP }, + where: { user: { id: user.id }, type: UserTokenType.TOTP }, }); } diff --git a/src/modules/features/login/login.controller.ts b/src/modules/static-front-end/login/login.controller.ts similarity index 90% rename from src/modules/features/login/login.controller.ts rename to src/modules/static-front-end/login/login.controller.ts index 02021ea..d966d92 100644 --- a/src/modules/features/login/login.controller.ts +++ b/src/modules/static-front-end/login/login.controller.ts @@ -76,9 +76,11 @@ export class LoginController { if (await this.totpService.userHasTOTP(user)) { const challenge = { type: 'verify', user: user.uuid, remember }; - req.session.challenge = await this.token.encryptChallenge(challenge); + const encrypted = await this.token.encryptChallenge(challenge); res.redirect( - '/login/verify' + (redirectTo ? '?redirectTo=' + redirectTo : ''), + `login/verify?challenge=${encrypted}${ + redirectTo ? '&redirectTo=' + redirectTo : '' + }`, ); return; } @@ -96,18 +98,19 @@ export class LoginController { @Get('verify') public verifyUserTokenView( - @Session() session: SessionData, @Req() req: Request, @Res() res: Response, - @Query('redirectTo') redirectTo?: string, + @Query() query: { redirectTo: string; challenge: string }, ) { - if (!session.challenge) { + if (!query.challenge) { req.flash('message', { error: true, text: 'An unexpected error occured, please log in again.', }); - res.redirect('/login' + (redirectTo ? '?redirectTo=' + redirectTo : '')); + res.redirect( + '/login' + (query.redirectTo ? '?redirectTo=' + query.redirectTo : ''), + ); return; } @@ -120,34 +123,36 @@ export class LoginController { @Body() body: { totp: string }, @Req() req: Request, @Res() res: Response, - @Query('redirectTo') redirectTo?: string, + @Query() query: { redirectTo: string; challenge: string }, ) { let user: User; let remember = false; try { - if (!session.challenge) { + if (!query.challenge) { throw new Error('No challenge'); } - const challenge = await this.token.decryptChallenge(session.challenge); - if (!challenge || challenge.type !== 'verify' || !challenge.user) { + const decrypted = await this.token.decryptChallenge(query.challenge); + if (!decrypted || decrypted.type !== 'verify' || !decrypted.user) { throw new Error('Bad challenge'); } - user = await this.userService.getByUUID(challenge.user); + user = await this.userService.getByUUID(decrypted.user); if (!user) { throw new Error('Bad challenge'); } - remember = challenge.remember; + remember = decrypted.remember; } catch (e: any) { req.flash('message', { error: true, text: 'An unexpected error occured, please log in again.', }); - res.redirect('/login' + (redirectTo ? '?redirectTo=' + redirectTo : '')); + res.redirect( + '/login' + (query.redirectTo ? '?redirectTo=' + query.redirectTo : ''), + ); return; } @@ -172,9 +177,8 @@ export class LoginController { req.session.cookie.maxAge = month; } - session.challenge = null; session.user = user.uuid; - res.redirect(redirectTo ? decodeURIComponent(redirectTo) : '/'); + res.redirect(query.redirectTo ? decodeURIComponent(query.redirectTo) : '/'); } @Get('activate') @@ -231,7 +235,6 @@ export class LoginController { public async recoverView( @Req() req: Request, @Res() res: Response, - @Session() session: SessionData, @Query() query: { token: string }, ) { if (query.token) { diff --git a/src/modules/features/login/login.module.ts b/src/modules/static-front-end/login/login.module.ts similarity index 100% rename from src/modules/features/login/login.module.ts rename to src/modules/static-front-end/login/login.module.ts diff --git a/src/modules/features/oauth2/oauth2.controller.ts b/src/modules/static-front-end/oauth2-router/oauth2-router.controller.ts similarity index 75% rename from src/modules/features/oauth2/oauth2.controller.ts rename to src/modules/static-front-end/oauth2-router/oauth2-router.controller.ts index 2e9f8e4..600b3ed 100644 --- a/src/modules/features/oauth2/oauth2.controller.ts +++ b/src/modules/static-front-end/oauth2-router/oauth2-router.controller.ts @@ -1,4 +1,3 @@ -import { OAuth2AccessToken } from '@icynet/oauth2-provider'; import { Controller, Get, @@ -7,10 +6,15 @@ import { Post, Req, Res, + UseGuards, } from '@nestjs/common'; import { NextFunction, Request, Response } from 'express'; +import { Scope } from 'src/decorators/scope.decorator'; +import { CurrentUser } from 'src/decorators/user.decorator'; +import { OAuth2Guard } from 'src/guards/oauth2.guard'; import { ConfigurationService } from 'src/modules/config/config.service'; -import { OAuth2Service } from './oauth2.service'; +import { User } from 'src/modules/objects/user/user.entity'; +import { OAuth2Service } from '../../oauth2/oauth2.service'; @Controller('oauth2') export class OAuth2Controller { @@ -61,15 +65,11 @@ export class OAuth2Controller { // TODO: Move to API @Get('user') + @UseGuards(OAuth2Guard) public async userInfo( - @Res({ passthrough: true }) res: Response, + @CurrentUser() user: User, + @Scope() scope: string, ): Promise> { - const token = res.locals.accessToken as OAuth2AccessToken; - const user = await this._service.userService.getById( - token.user_id as number, - ['picture', 'privileges'], - ); - if (!user) { throw new NotFoundException('No such user'); } @@ -86,13 +86,13 @@ export class OAuth2Controller { nickname: user.display_name, }; - if (token.scope.includes('email') || token.scope.includes('user:email')) { + if (scope.includes('email') || scope.includes('user:email')) { userData.email = user.email; userData.email_verified = true; } if ( - (token.scope.includes('image') || token.scope.includes('user:image')) && + (scope.includes('image') || scope.includes('user:image')) && user.picture ) { userData.image = `${this._config.get('app.base_url')}/uploads/${ @@ -102,10 +102,10 @@ export class OAuth2Controller { } if ( - token.scope.includes('privileges') || - (token.scope.includes('user:privileges') && user.privileges?.length) + scope.includes('privileges') || + (scope.includes('user:privileges') && user.privileges?.length) ) { - userData.privileges = user.privileges; + userData.privileges = user.privileges.map(({ name }) => name); } return userData; diff --git a/src/modules/static-front-end/oauth2-router/oauth2-router.module.ts b/src/modules/static-front-end/oauth2-router/oauth2-router.module.ts new file mode 100644 index 0000000..5766c2e --- /dev/null +++ b/src/modules/static-front-end/oauth2-router/oauth2-router.module.ts @@ -0,0 +1,22 @@ +import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; +import { AuthMiddleware } from 'src/middleware/auth.middleware'; +import { ValidateCSRFMiddleware } from 'src/middleware/validate-csrf.middleware'; +import { OAuth2Module } from 'src/modules/oauth2/oauth2.module'; +import { OAuth2Service } from 'src/modules/oauth2/oauth2.service'; +import { UserModule } from 'src/modules/objects/user/user.module'; +import { OAuth2Controller } from './oauth2-router.controller'; + +@Module({ + controllers: [OAuth2Controller], + imports: [OAuth2Module, UserModule], +}) +export class OAuth2RouterModule implements NestModule { + constructor(private _service: OAuth2Service) {} + + configure(consumer: MiddlewareConsumer) { + consumer.apply(this._service.oauth.express()).forRoutes('oauth2/*'); + consumer + .apply(AuthMiddleware, ValidateCSRFMiddleware) + .forRoutes('oauth2/authorize'); + } +} diff --git a/src/modules/features/register/register.controller.ts b/src/modules/static-front-end/register/register.controller.ts similarity index 100% rename from src/modules/features/register/register.controller.ts rename to src/modules/static-front-end/register/register.controller.ts diff --git a/src/modules/features/register/register.interfaces.ts b/src/modules/static-front-end/register/register.interfaces.ts similarity index 100% rename from src/modules/features/register/register.interfaces.ts rename to src/modules/static-front-end/register/register.interfaces.ts diff --git a/src/modules/features/register/register.module.ts b/src/modules/static-front-end/register/register.module.ts similarity index 100% rename from src/modules/features/register/register.module.ts rename to src/modules/static-front-end/register/register.module.ts diff --git a/src/modules/features/settings/settings.controller.ts b/src/modules/static-front-end/settings/settings.controller.ts similarity index 100% rename from src/modules/features/settings/settings.controller.ts rename to src/modules/static-front-end/settings/settings.controller.ts diff --git a/src/modules/features/settings/settings.module.ts b/src/modules/static-front-end/settings/settings.module.ts similarity index 97% rename from src/modules/features/settings/settings.module.ts rename to src/modules/static-front-end/settings/settings.module.ts index 3428a3b..ab300ae 100644 --- a/src/modules/features/settings/settings.module.ts +++ b/src/modules/static-front-end/settings/settings.module.ts @@ -15,7 +15,7 @@ import { ConfigurationModule } from 'src/modules/config/config.module'; import { ConfigurationService } from 'src/modules/config/config.service'; import { UploadModule } from 'src/modules/objects/upload/upload.module'; import { UserModule } from 'src/modules/objects/user/user.module'; -import { OAuth2Module } from '../oauth2/oauth2.module'; +import { OAuth2Module } from '../../oauth2/oauth2.module'; import { SettingsController } from './settings.controller'; import { SettingsService } from './settings.service'; import { OAuth2ClientModule } from 'src/modules/objects/oauth2-client/oauth2-client.module'; diff --git a/src/modules/features/settings/settings.service.ts b/src/modules/static-front-end/settings/settings.service.ts similarity index 83% rename from src/modules/features/settings/settings.service.ts rename to src/modules/static-front-end/settings/settings.service.ts index c0d5c2e..f545aa6 100644 --- a/src/modules/features/settings/settings.service.ts +++ b/src/modules/static-front-end/settings/settings.service.ts @@ -1,6 +1,6 @@ import { ConfigurationService } from 'src/modules/config/config.service'; import { UserService } from 'src/modules/objects/user/user.service'; -import { OAuth2Service } from '../oauth2/oauth2.service'; +import { OAuth2Service } from '../../oauth2/oauth2.service'; export class SettingsService { constructor( diff --git a/src/modules/static-front-end/static-front-end.module.ts b/src/modules/static-front-end/static-front-end.module.ts new file mode 100644 index 0000000..9ada73a --- /dev/null +++ b/src/modules/static-front-end/static-front-end.module.ts @@ -0,0 +1,46 @@ +import { + MiddlewareConsumer, + Module, + NestModule, + RequestMethod, +} from '@nestjs/common'; +import { CSRFMiddleware } from 'src/middleware/csrf.middleware'; +import { UserMiddleware } from 'src/middleware/user.middleware'; +import { ConfigurationModule } from '../config/config.module'; +import { JWTModule } from '../jwt/jwt.module'; +import { ObjectsModule } from '../objects/objects.module'; + +import { LoginModule } from './login/login.module'; +import { OAuth2RouterModule } from './oauth2-router/oauth2-router.module'; +import { RegisterModule } from './register/register.module'; +import { SettingsModule } from './settings/settings.module'; +import { TwoFactorModule } from './two-factor/two-factor.module'; + +@Module({ + imports: [ + ConfigurationModule, + JWTModule, + + ObjectsModule, + + LoginModule, + OAuth2RouterModule, + RegisterModule, + SettingsModule, + TwoFactorModule, + ], + providers: [], + exports: [], +}) +export class StaticFrontEndModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + consumer + .apply(CSRFMiddleware, UserMiddleware) + .exclude( + { path: 'uploads*', method: RequestMethod.ALL }, + { path: 'public*', method: RequestMethod.ALL }, + { path: 'api*', method: RequestMethod.ALL }, + ) + .forRoutes('*'); + } +} diff --git a/src/modules/features/two-factor/two-factor.controller.ts b/src/modules/static-front-end/two-factor/two-factor.controller.ts similarity index 100% rename from src/modules/features/two-factor/two-factor.controller.ts rename to src/modules/static-front-end/two-factor/two-factor.controller.ts diff --git a/src/modules/features/two-factor/two-factor.module.ts b/src/modules/static-front-end/two-factor/two-factor.module.ts similarity index 100% rename from src/modules/features/two-factor/two-factor.module.ts rename to src/modules/static-front-end/two-factor/two-factor.module.ts diff --git a/views/login/totp-verify.pug b/views/login/totp-verify.pug index 6ac58d1..145dc8f 100644 --- a/views/login/totp-verify.pug +++ b/views/login/totp-verify.pug @@ -16,9 +16,9 @@ block body .alert.alert-success span #{message.text} - form(method="post") + form(method="post", autocomplete="off") div.form-container input#csrf(type="hidden", name="_csrf", value=csrf) label.form-label(for="totp") Code - input.form-control#totp(type="text", name="totp", autofocus, placeholder="xxxxxx") + input.form-control#totp(type="text", autocomplete="off", name="totp", autofocus, placeholder="xxxxxx") button.btn.btn-primary(type="submit") Log in