init
This commit is contained in:
commit
70c35f6d2b
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.vscode
|
||||
.idea
|
||||
dist
|
||||
node_modules
|
||||
6
.npmignore
Normal file
6
.npmignore
Normal file
@ -0,0 +1,6 @@
|
||||
.vscode
|
||||
.idea
|
||||
node_modules
|
||||
src
|
||||
.gitignore
|
||||
tsconfig.json
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Markus Morgenstern
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
94
README.md
Normal file
94
README.md
Normal file
@ -0,0 +1,94 @@
|
||||
# Token Authentication
|
||||
|
||||
This Package contains a Token Authentication Module for NestJs with Organizations UserAccounts Groups and Rights access.
|
||||
|
||||
## Required external Services
|
||||
|
||||
You have to implement these Interfaces and give the Implementations into the Dynamic Module.
|
||||
|
||||
| Service | Injection Token | Interface in Contracts | Description |
|
||||
| ----------------------- | ----------------------------------- | ------------------------ | -------------------------------------------------------------- |
|
||||
| Token Service | @apihub24/token_service | TokenService | a Service that generates the Token for authentication |
|
||||
| Password Service | @apihub24/password_service | PasswordService | a Service that hash the Password |
|
||||
| Session Service | @apihub24/session_service | SessionService | a Service that handles Session Get, Save, Delete Operations |
|
||||
| Mail Service | @apihub24/mail_verification_service | MailVerificationService | a Service that handles Send Verify Mail and the verify Request |
|
||||
| Account Repository | @apihub24/account_repository | Repository<Account> | a Service that handles Session Get, Save, Delete Operations |
|
||||
| Group Repository | @apihub24/group_repository | Repository<Group> | a Service that handles Session Get, Save, Delete Operations |
|
||||
| Right Repository | @apihub24/right_repository | Repository<Right> | a Service that handles Session Get, Save, Delete Operations |
|
||||
| Organization Repository | @apihub24/organization_repository | Repository<Organization> | a Service that handles Session Get, Save, Delete Operations |
|
||||
|
||||
If you want to use a InMemory Repository you not have to implement itself you can use the package [@apihub24/in-memory-repository](https://www.npmjs.com/package/@apihub24/in-memory-repository).
|
||||
|
||||
## Example
|
||||
|
||||
```typescript
|
||||
import { Module } from "@nestjs/common";
|
||||
import {
|
||||
Account,
|
||||
Group,
|
||||
Organization,
|
||||
Right,
|
||||
TokenAuthenticationModule,
|
||||
} from "@apihub24/token-authentication";
|
||||
import { LoginController } from "./controllers/login.controller";
|
||||
import { LogoutController } from "./controllers/logout.controller";
|
||||
import { ConfigModule } from "@nestjs/config";
|
||||
// a implementation of the EmailService
|
||||
import { EmailVerificationModule } from "@apihub24/email-verification";
|
||||
// a implementation of the InMemoryRepository used for Repositories
|
||||
import { InMemoryRepository } from "@apihub24/in-memory-repository";
|
||||
// a implementation of the SessionService
|
||||
import { InMemorySessionsModule } from "@apihub24/in-memory-sessions";
|
||||
// a implementation of the TokenService
|
||||
import { JwtGeneratorModule } from "@apihub24/jwt-generator";
|
||||
// a implementation of the PasswordService
|
||||
import { PasswordHasherModule } from "@apihub24/password-hasher";
|
||||
import { takeExportedProviders } from "@apihub24/nestjs-helper";
|
||||
|
||||
// use the Dynamic Modules from the Implementations
|
||||
const emailVerificationModule = EmailVerificationModule.forRoot();
|
||||
const sessionModule = InMemorySessionsModule.forRoot();
|
||||
// JwtGeneratorModule requires the SessionModule so give them the Providers
|
||||
const jwtModule = JwtGeneratorModule.forRoot([
|
||||
...takeExportedProviders(sessionModule),
|
||||
]);
|
||||
const passwordModule = PasswordHasherModule.forRoot();
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot(),
|
||||
emailVerificationModule,
|
||||
sessionModule,
|
||||
jwtModule,
|
||||
passwordModule,
|
||||
TokenAuthenticationModule.forRoot([
|
||||
...takeExportedProviders(emailVerificationModule),
|
||||
...takeExportedProviders(sessionModule),
|
||||
...takeExportedProviders(jwtModule),
|
||||
...takeExportedProviders(passwordModule),
|
||||
// register the Repositories with InMemoryRepository
|
||||
{
|
||||
provide: "@apihub24/organization_repository",
|
||||
useClass: InMemoryRepository<Organization>,
|
||||
},
|
||||
{
|
||||
provide: "@apihub24/account_repository",
|
||||
useClass: InMemoryRepository<Account>,
|
||||
},
|
||||
{
|
||||
provide: "@apihub24/group_repository",
|
||||
useClass: InMemoryRepository<Group>,
|
||||
},
|
||||
{
|
||||
provide: "@apihub24/right_repository",
|
||||
useClass: InMemoryRepository<Right>,
|
||||
},
|
||||
]),
|
||||
],
|
||||
controllers: [LoginController, LogoutController],
|
||||
providers: [],
|
||||
// required exports for the TokenGuard
|
||||
exports: [TokenAuthenticationModule, jwtModule],
|
||||
})
|
||||
export class AuthenticationModule {}
|
||||
```
|
||||
454
package-lock.json
generated
Normal file
454
package-lock.json
generated
Normal file
@ -0,0 +1,454 @@
|
||||
{
|
||||
"name": "@apihub24/token-authentication",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@apihub24/token-authentication",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@apihub24/repository": "^1.0.1",
|
||||
"@nestjs/common": "^11.1.6",
|
||||
"@nestjs/config": "^4.0.2",
|
||||
"@nestjs/core": "^11.1.6",
|
||||
"class-validator": "^0.14.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@apihub24/repository": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@apihub24/repository/-/repository-1.0.1.tgz",
|
||||
"integrity": "sha512-ex3Z+lxsHtVKDTolJQqLHswq9SKfXzM/hWv17zsrhKqJwuGxO7CeBFM60aiuApZX9NqBhGAkPGGj9jt+F/Y9HQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@borewit/text-codec": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz",
|
||||
"integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/@lukeed/csprng": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz",
|
||||
"integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/common": {
|
||||
"version": "11.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.6.tgz",
|
||||
"integrity": "sha512-krKwLLcFmeuKDqngG2N/RuZHCs2ycsKcxWIDgcm7i1lf3sQ0iG03ci+DsP/r3FcT/eJDFsIHnKtNta2LIi7PzQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"file-type": "21.0.0",
|
||||
"iterare": "1.2.1",
|
||||
"load-esm": "1.0.2",
|
||||
"tslib": "2.8.1",
|
||||
"uid": "2.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/nest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"class-transformer": ">=0.4.1",
|
||||
"class-validator": ">=0.13.2",
|
||||
"reflect-metadata": "^0.1.12 || ^0.2.0",
|
||||
"rxjs": "^7.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"class-transformer": {
|
||||
"optional": true
|
||||
},
|
||||
"class-validator": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/config": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.2.tgz",
|
||||
"integrity": "sha512-McMW6EXtpc8+CwTUwFdg6h7dYcBUpH5iUILCclAsa+MbCEvC9ZKu4dCHRlJqALuhjLw97pbQu62l4+wRwGeZqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dotenv": "16.4.7",
|
||||
"dotenv-expand": "12.0.1",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
||||
"rxjs": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/core": {
|
||||
"version": "11.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.6.tgz",
|
||||
"integrity": "sha512-siWX7UDgErisW18VTeJA+x+/tpNZrJewjTBsRPF3JVxuWRuAB1kRoiJcxHgln8Lb5UY9NdvklITR84DUEXD0Cg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nuxt/opencollective": "0.4.1",
|
||||
"fast-safe-stringify": "2.1.1",
|
||||
"iterare": "1.2.1",
|
||||
"path-to-regexp": "8.2.0",
|
||||
"tslib": "2.8.1",
|
||||
"uid": "2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/nest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^11.0.0",
|
||||
"@nestjs/microservices": "^11.0.0",
|
||||
"@nestjs/platform-express": "^11.0.0",
|
||||
"@nestjs/websockets": "^11.0.0",
|
||||
"reflect-metadata": "^0.1.12 || ^0.2.0",
|
||||
"rxjs": "^7.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@nestjs/microservices": {
|
||||
"optional": true
|
||||
},
|
||||
"@nestjs/platform-express": {
|
||||
"optional": true
|
||||
},
|
||||
"@nestjs/websockets": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@nuxt/opencollective": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/opencollective/-/opencollective-0.4.1.tgz",
|
||||
"integrity": "sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"consola": "^3.2.3"
|
||||
},
|
||||
"bin": {
|
||||
"opencollective": "bin/opencollective.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.10.0",
|
||||
"npm": ">=5.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tokenizer/inflate": {
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz",
|
||||
"integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.4.0",
|
||||
"fflate": "^0.8.2",
|
||||
"token-types": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/@tokenizer/token": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
|
||||
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/validator": {
|
||||
"version": "13.15.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.2.tgz",
|
||||
"integrity": "sha512-y7pa/oEJJ4iGYBxOpfAKn5b9+xuihvzDVnC/OSvlVnGxVg0pOqmjiMafiJ1KVNQEaPZf9HsEp5icEwGg8uIe5Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/class-validator": {
|
||||
"version": "0.14.2",
|
||||
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz",
|
||||
"integrity": "sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/validator": "^13.11.8",
|
||||
"libphonenumber-js": "^1.11.1",
|
||||
"validator": "^13.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/consola": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
|
||||
"integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.4.7",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
|
||||
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv-expand": {
|
||||
"version": "12.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.1.tgz",
|
||||
"integrity": "sha512-LaKRbou8gt0RNID/9RoI+J2rvXsBRPMV7p+ElHlPhcSARbCPDYcYG2s1TIzAfWv4YSgyY5taidWzzs31lNV3yQ==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"dotenv": "^16.4.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-safe-stringify": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
|
||||
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fflate": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
|
||||
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/file-type": {
|
||||
"version": "21.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-21.0.0.tgz",
|
||||
"integrity": "sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tokenizer/inflate": "^0.2.7",
|
||||
"strtok3": "^10.2.2",
|
||||
"token-types": "^6.0.0",
|
||||
"uint8array-extras": "^1.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/iterare": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz",
|
||||
"integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/libphonenumber-js": {
|
||||
"version": "1.12.13",
|
||||
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.13.tgz",
|
||||
"integrity": "sha512-QZXnR/OGiDcBjF4hGk0wwVrPcZvbSSyzlvkjXv5LFfktj7O2VZDrt4Xs8SgR/vOFco+qk1i8J43ikMXZoTrtPw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/load-esm": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.2.tgz",
|
||||
"integrity": "sha512-nVAvWk/jeyrWyXEAs84mpQCYccxRqgKY4OznLuJhJCa0XsPSfdOIr2zvBZEj3IHEHbX97jjscKRRV539bW0Gpw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
},
|
||||
{
|
||||
"type": "buymeacoffee",
|
||||
"url": "https://buymeacoffee.com/borewit"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=13.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
|
||||
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/reflect-metadata": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
|
||||
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/rxjs": {
|
||||
"version": "7.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strtok3": {
|
||||
"version": "10.3.4",
|
||||
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz",
|
||||
"integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tokenizer/token": "^0.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/token-types": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz",
|
||||
"integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@borewit/text-codec": "^0.1.0",
|
||||
"@tokenizer/token": "^0.3.0",
|
||||
"ieee754": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
||||
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/uid": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz",
|
||||
"integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@lukeed/csprng": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/uint8array-extras": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.1.tgz",
|
||||
"integrity": "sha512-+NWHrac9dvilNgme+gP4YrBSumsaMZP0fNBtXXFIf33RLLKEcBUKaQZ7ULUbS0sBfcjxIZ4V96OTRkCbM7hxpw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/validator": {
|
||||
"version": "13.15.15",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz",
|
||||
"integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
package.json
Normal file
30
package.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "@apihub24/token-authentication",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^11.1.6",
|
||||
"@nestjs/config": "^4.0.2",
|
||||
"@nestjs/core": "^11.1.6",
|
||||
"@apihub24/repository": "^1.0.1",
|
||||
"class-validator": "^0.14.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.2"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": {
|
||||
"email": "markusmorgenstern87@outlook.de",
|
||||
"name": "Markus Morgenstern",
|
||||
"url": "https://git.apihub24.de/"
|
||||
},
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
12
src/contracts/index.ts
Normal file
12
src/contracts/index.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export * from "./models/account";
|
||||
export * from "./models/group";
|
||||
export * from "./models/organization";
|
||||
export * from "./models/registration";
|
||||
export * from "./models/right";
|
||||
export * from "./models/session";
|
||||
export * from "./models/sign.in";
|
||||
|
||||
export * from "./services/mail.verification.service";
|
||||
export * from "./services/password.service";
|
||||
export * from "./services/session.service";
|
||||
export * from "./services/token.service";
|
||||
28
src/contracts/models/account.ts
Normal file
28
src/contracts/models/account.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { Group } from './group';
|
||||
import {
|
||||
IsEmail,
|
||||
IsNotEmpty,
|
||||
MinLength,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
|
||||
export class Account {
|
||||
id: string;
|
||||
|
||||
@MinLength(3)
|
||||
accountName: string;
|
||||
|
||||
@MinLength(3)
|
||||
passwordHash: string;
|
||||
|
||||
@IsEmail()
|
||||
email: string;
|
||||
|
||||
emailVerified: boolean;
|
||||
|
||||
active: boolean;
|
||||
|
||||
@IsNotEmpty({ each: true })
|
||||
@ValidateNested({ each: true })
|
||||
groups: Group[];
|
||||
}
|
||||
18
src/contracts/models/group.ts
Normal file
18
src/contracts/models/group.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Organization } from './organization';
|
||||
import { Right } from './right';
|
||||
import { IsNotEmpty, MinLength, ValidateNested } from 'class-validator';
|
||||
|
||||
export class Group {
|
||||
id: string;
|
||||
|
||||
@MinLength(3)
|
||||
name: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@ValidateNested()
|
||||
organization: Organization;
|
||||
|
||||
@IsNotEmpty({ each: true })
|
||||
@ValidateNested({ each: true })
|
||||
rights: Right[];
|
||||
}
|
||||
9
src/contracts/models/organization.ts
Normal file
9
src/contracts/models/organization.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { IsNotEmpty, MinLength } from 'class-validator';
|
||||
|
||||
export class Organization {
|
||||
id: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@MinLength(3)
|
||||
name: string;
|
||||
}
|
||||
22
src/contracts/models/registration.ts
Normal file
22
src/contracts/models/registration.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator';
|
||||
|
||||
export class Registration {
|
||||
// @IsNotEmpty()
|
||||
@MinLength(3)
|
||||
accountName: string;
|
||||
|
||||
// @IsNotEmpty()
|
||||
@MinLength(3)
|
||||
password: string;
|
||||
|
||||
// @IsNotEmpty()
|
||||
@MinLength(3)
|
||||
passwordComparer: string;
|
||||
|
||||
// @IsNotEmpty()
|
||||
@IsEmail()
|
||||
email: string;
|
||||
|
||||
@IsNotEmpty({ each: true })
|
||||
groupIds: string[];
|
||||
}
|
||||
8
src/contracts/models/right.ts
Normal file
8
src/contracts/models/right.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { MinLength } from 'class-validator';
|
||||
|
||||
export class Right {
|
||||
id: string;
|
||||
|
||||
@MinLength(3)
|
||||
name: string;
|
||||
}
|
||||
12
src/contracts/models/session.ts
Normal file
12
src/contracts/models/session.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { IsNotEmpty, ValidateNested } from "class-validator";
|
||||
import { Account } from "./account";
|
||||
|
||||
export class Session {
|
||||
id: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@ValidateNested()
|
||||
account: Account;
|
||||
|
||||
metaData: Record<string, any>;
|
||||
}
|
||||
8
src/contracts/models/sign.in.ts
Normal file
8
src/contracts/models/sign.in.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { MinLength } from 'class-validator';
|
||||
|
||||
export class SignIn {
|
||||
@MinLength(3)
|
||||
accountName: string;
|
||||
@MinLength(3)
|
||||
password: string;
|
||||
}
|
||||
6
src/contracts/services/mail.verification.service.ts
Normal file
6
src/contracts/services/mail.verification.service.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { Account } from "../models/account";
|
||||
|
||||
export interface MailVerificationService {
|
||||
sendVerificationMail(account: Account): Promise<void>;
|
||||
verify(email: string, code: string): Promise<boolean>;
|
||||
}
|
||||
4
src/contracts/services/password.service.ts
Normal file
4
src/contracts/services/password.service.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface PasswordService {
|
||||
hash(plainTextPassword: string): Promise<string>;
|
||||
verify(plainTextPassword: string, passwordHash: string): Promise<boolean>;
|
||||
}
|
||||
9
src/contracts/services/session.service.ts
Normal file
9
src/contracts/services/session.service.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Account } from "../models/account";
|
||||
import { Session } from "../models/session";
|
||||
|
||||
export interface SessionService {
|
||||
create(account: Account): Promise<Session>;
|
||||
getBy(filter: (account: Account) => boolean): Promise<Session[]>;
|
||||
getById(sessionId: string): Promise<Session | null>;
|
||||
remove(sessionId: string): Promise<void>;
|
||||
}
|
||||
8
src/contracts/services/token.service.ts
Normal file
8
src/contracts/services/token.service.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Account } from "../models/account";
|
||||
import { Session } from "../models/session";
|
||||
|
||||
export interface TokenService {
|
||||
generate(session: Session): Promise<string>;
|
||||
validate(token: string): Promise<boolean>;
|
||||
getAccount(token: string): Promise<Account | null>;
|
||||
}
|
||||
4
src/guards/index.ts
Normal file
4
src/guards/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from "./organization.decorator";
|
||||
export * from "./rights.decorator";
|
||||
export * from "./roles.decorator";
|
||||
export * from "./token.guard";
|
||||
5
src/guards/organization.decorator.ts
Normal file
5
src/guards/organization.decorator.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
|
||||
export const ORGANIZATIONS_KEY = 'organizations';
|
||||
export const Organizations = (...organizations: string[]) =>
|
||||
SetMetadata(ORGANIZATIONS_KEY, organizations);
|
||||
4
src/guards/rights.decorator.ts
Normal file
4
src/guards/rights.decorator.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
|
||||
export const RIGHTS_KEY = 'rights';
|
||||
export const Rights = (...rights: string[]) => SetMetadata(RIGHTS_KEY, rights);
|
||||
4
src/guards/roles.decorator.ts
Normal file
4
src/guards/roles.decorator.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
|
||||
export const ROLES_KEY = 'roles';
|
||||
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
|
||||
78
src/guards/token.guard.ts
Normal file
78
src/guards/token.guard.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Inject,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { RIGHTS_KEY } from './rights.decorator';
|
||||
import { ROLES_KEY } from './roles.decorator';
|
||||
import * as tokenService from '../contracts/services/token.service';
|
||||
import { ORGANIZATIONS_KEY } from './organization.decorator';
|
||||
|
||||
@Injectable()
|
||||
export class TokenGuard implements CanActivate {
|
||||
constructor(
|
||||
private reflector: Reflector,
|
||||
@Inject('@apihub24/token_service')
|
||||
private readonly tokenService: tokenService.TokenService,
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const requiredRights = this.reflector.getAllAndOverride<string[]>(
|
||||
RIGHTS_KEY,
|
||||
[context.getHandler(), context.getClass()],
|
||||
);
|
||||
const requiredRoles = this.reflector.getAllAndOverride<string[]>(
|
||||
ROLES_KEY,
|
||||
[context.getHandler(), context.getClass()],
|
||||
);
|
||||
const requiredOrganizations = this.reflector.getAllAndOverride<string[]>(
|
||||
ORGANIZATIONS_KEY,
|
||||
[context.getHandler(), context.getClass()],
|
||||
);
|
||||
if (!requiredRights && !requiredRoles && !requiredOrganizations) {
|
||||
return true;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const {
|
||||
headers: { authorization },
|
||||
} = context.switchToHttp().getRequest();
|
||||
if (
|
||||
!authorization ||
|
||||
typeof authorization !== 'string' ||
|
||||
!authorization.includes('Bearer ')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const token = authorization.slice(7);
|
||||
const account = await this.tokenService.getAccount(token);
|
||||
if (!account) {
|
||||
return false;
|
||||
}
|
||||
if (requiredOrganizations) {
|
||||
for (const orga of requiredOrganizations) {
|
||||
if (!account.groups.find((x) => x.organization.name === orga)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (requiredRoles) {
|
||||
for (const role of requiredRoles) {
|
||||
if (!account.groups.find((x) => x.name === role)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (requiredRights) {
|
||||
for (const right of requiredRights) {
|
||||
if (
|
||||
!account.groups.find((x) => x.rights.find((y) => y.name === right))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
30
src/index.ts
Normal file
30
src/index.ts
Normal file
@ -0,0 +1,30 @@
|
||||
export * from "./token-authentication.module";
|
||||
|
||||
export * from "./contracts/models/account";
|
||||
export * from "./contracts/models/group";
|
||||
export * from "./contracts/models/organization";
|
||||
export * from "./contracts/models/registration";
|
||||
export * from "./contracts/models/right";
|
||||
export * from "./contracts/models/session";
|
||||
export * from "./contracts/models/sign.in";
|
||||
|
||||
export * from "./contracts/services/mail.verification.service";
|
||||
export * from "./contracts/services/password.service";
|
||||
export * from "./contracts/services/session.service";
|
||||
export * from "./contracts/services/token.service";
|
||||
|
||||
export * from "./guards/organization.decorator";
|
||||
export * from "./guards/rights.decorator";
|
||||
export * from "./guards/roles.decorator";
|
||||
export * from "./guards/token.guard";
|
||||
|
||||
export * from "./services/account.factory.service";
|
||||
export * from "./services/account.service";
|
||||
export * from "./services/group.factory.service";
|
||||
export * from "./services/group.service";
|
||||
export * from "./services/login.service";
|
||||
export * from "./services/organization.factory.service";
|
||||
export * from "./services/organization.service";
|
||||
export * from "./services/registration.service";
|
||||
export * from "./services/right.factory.service";
|
||||
export * from "./services/right.service";
|
||||
40
src/services/account.factory.service.ts
Normal file
40
src/services/account.factory.service.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { Inject, Injectable } from "@nestjs/common";
|
||||
import { validate } from "class-validator";
|
||||
import * as contracts from "src/contracts";
|
||||
import { GroupService } from "./group.service";
|
||||
|
||||
@Injectable()
|
||||
export class AccountFactoryService {
|
||||
constructor(
|
||||
@Inject("@apihub24/password_service")
|
||||
private readonly passwordService: contracts.PasswordService,
|
||||
@Inject(GroupService)
|
||||
private readonly groupService: GroupService
|
||||
) {}
|
||||
|
||||
async createFromRegistration(
|
||||
registration: contracts.Registration
|
||||
): Promise<contracts.Account> {
|
||||
let validationErrors = await validate(registration);
|
||||
if (validationErrors?.length) {
|
||||
throw new Error(validationErrors[0].toString());
|
||||
}
|
||||
const groups = await this.groupService.getBy((x) =>
|
||||
registration.groupIds.includes(x.id)
|
||||
);
|
||||
const account = new contracts.Account();
|
||||
account.accountName = registration.accountName;
|
||||
account.email = registration.email;
|
||||
account.emailVerified = false;
|
||||
account.passwordHash = await this.passwordService.hash(
|
||||
registration.password
|
||||
);
|
||||
account.active = false;
|
||||
account.groups = groups;
|
||||
validationErrors = await validate(account);
|
||||
if (validationErrors?.length) {
|
||||
throw new Error(validationErrors[0].toString());
|
||||
}
|
||||
return account;
|
||||
}
|
||||
}
|
||||
71
src/services/account.service.ts
Normal file
71
src/services/account.service.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { Account } from "../contracts/models/account";
|
||||
import { Inject, Injectable } from "@nestjs/common";
|
||||
import * as repository from "@apihub24/repository";
|
||||
import { Group } from "src/contracts";
|
||||
|
||||
@Injectable()
|
||||
export class AccountService {
|
||||
constructor(
|
||||
@Inject("@apihub24/account_repository")
|
||||
private readonly accountRepository: repository.Repository<Account>,
|
||||
@Inject("@apihub24/group_repository")
|
||||
private readonly groupRepository: repository.Repository<Group>
|
||||
) {}
|
||||
|
||||
async getBy(filter: (account: Account) => boolean): Promise<Account[]> {
|
||||
return await this.accountRepository.getBy(filter);
|
||||
}
|
||||
|
||||
async save(account: Account): Promise<Account | null> {
|
||||
const accounts = await this.accountRepository.save([account]);
|
||||
return accounts?.length ? accounts[0] : null;
|
||||
}
|
||||
|
||||
async delete(filter: (account: Account) => boolean): Promise<boolean> {
|
||||
return await this.accountRepository.deleteBy(filter);
|
||||
}
|
||||
|
||||
async addAccountToGroup(
|
||||
accountId: string,
|
||||
groupId: string
|
||||
): Promise<Account> {
|
||||
const [account, group] = await this.getAccountAndGroup(accountId, groupId);
|
||||
account.groups = account.groups.filter((x) => x.id !== groupId);
|
||||
account.groups.push(group);
|
||||
const accountsSaved = await this.accountRepository.save([account]);
|
||||
if (!accountsSaved.length) {
|
||||
throw new Error(`account ${account.accountName} can not be saved`);
|
||||
}
|
||||
return accountsSaved[0];
|
||||
}
|
||||
|
||||
async removeAccountFromGroup(
|
||||
accountId: string,
|
||||
groupId: string
|
||||
): Promise<Account> {
|
||||
const [account] = await this.getAccountAndGroup(accountId, groupId);
|
||||
account.groups = account.groups.filter((x) => x.id !== groupId);
|
||||
const accountsSaved = await this.accountRepository.save([account]);
|
||||
if (!accountsSaved.length) {
|
||||
throw new Error(`account ${account.accountName} can not be saved`);
|
||||
}
|
||||
return accountsSaved[0];
|
||||
}
|
||||
|
||||
private async getAccountAndGroup(
|
||||
accountId: string,
|
||||
groupId: string
|
||||
): Promise<[Account, Group]> {
|
||||
const accounts = await this.accountRepository.getBy(
|
||||
(x) => x.id === accountId
|
||||
);
|
||||
if (!accounts.length) {
|
||||
throw new Error(`account with id ${accountId} not found`);
|
||||
}
|
||||
const groups = await this.groupRepository.getBy((x) => x.id === groupId);
|
||||
if (!groups.length) {
|
||||
throw new Error(`group with id ${groupId} not found`);
|
||||
}
|
||||
return [accounts[0], groups[0]];
|
||||
}
|
||||
}
|
||||
40
src/services/group.factory.service.ts
Normal file
40
src/services/group.factory.service.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { Inject, Injectable } from "@nestjs/common";
|
||||
import { validate } from "class-validator";
|
||||
import { OrganizationService } from "./organization.service";
|
||||
import { RightService } from "./right.service";
|
||||
import { Group } from "src/contracts";
|
||||
|
||||
@Injectable()
|
||||
export class GroupFactoryService {
|
||||
constructor(
|
||||
@Inject(RightService)
|
||||
private readonly rightService: RightService,
|
||||
@Inject(OrganizationService)
|
||||
private readonly organizationService: OrganizationService
|
||||
) {}
|
||||
|
||||
async createGroup(
|
||||
name: string,
|
||||
organizationId: string,
|
||||
rightIds: string[]
|
||||
): Promise<Group> {
|
||||
const rights = await this.rightService.getBy((x) =>
|
||||
rightIds.includes(x.id)
|
||||
);
|
||||
const organizations = await this.organizationService.getBy(
|
||||
(x) => x.id === organizationId
|
||||
);
|
||||
if (!organizations?.length) {
|
||||
throw new Error(`organization with id (${organizationId}) not exists`);
|
||||
}
|
||||
const group = new Group();
|
||||
group.name = name;
|
||||
group.organization = organizations[0];
|
||||
group.rights = rights;
|
||||
const validationErrors = await validate(group);
|
||||
if (validationErrors?.length) {
|
||||
throw new Error(validationErrors[0].toString());
|
||||
}
|
||||
return group;
|
||||
}
|
||||
}
|
||||
64
src/services/group.service.ts
Normal file
64
src/services/group.service.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { Inject, Injectable } from "@nestjs/common";
|
||||
import { Group } from "../contracts/models/group";
|
||||
import * as repository from "@apihub24/repository";
|
||||
import { Right } from "src/contracts";
|
||||
|
||||
@Injectable()
|
||||
export class GroupService {
|
||||
constructor(
|
||||
@Inject("@apihub24/group_repository")
|
||||
private readonly groupRepository: repository.Repository<Group>,
|
||||
@Inject("@apihub24/right_repository")
|
||||
private readonly rightRepository: repository.Repository<Right>
|
||||
) {}
|
||||
|
||||
async getBy(filter: (group: Group) => boolean): Promise<Group[]> {
|
||||
return await this.groupRepository.getBy(filter);
|
||||
}
|
||||
|
||||
async save(group: Group): Promise<Group | null> {
|
||||
const groups = await this.groupRepository.save([group]);
|
||||
return groups?.length ? groups[0] : null;
|
||||
}
|
||||
|
||||
async delete(filter: (group: Group) => boolean): Promise<boolean> {
|
||||
return this.groupRepository.deleteBy(filter);
|
||||
}
|
||||
|
||||
async addRightToGroup(groupId: string, rightId: string) {
|
||||
const [group, right] = await this.getGroupAndRight(groupId, rightId);
|
||||
group.rights = group.rights.filter((x) => x.id !== rightId);
|
||||
group.rights.push(right);
|
||||
const savedGroups = await this.groupRepository.save([group]);
|
||||
if (!savedGroups.length) {
|
||||
throw new Error(`group ${group.name} can not be saved`);
|
||||
}
|
||||
return savedGroups[0];
|
||||
}
|
||||
|
||||
async removeRightFromGroup(groupId: string, rightId: string) {
|
||||
const [group] = await this.getGroupAndRight(groupId, rightId);
|
||||
group.rights = group.rights.filter((x) => x.id !== rightId);
|
||||
const savedGroups = await this.groupRepository.save([group]);
|
||||
if (!savedGroups.length) {
|
||||
throw new Error(`group ${group.name} can not be saved`);
|
||||
}
|
||||
return savedGroups[0];
|
||||
}
|
||||
|
||||
private async getGroupAndRight(
|
||||
groupId: string,
|
||||
rightId: string
|
||||
): Promise<[Group, Right]> {
|
||||
const groups = await this.groupRepository.getBy((x) => x.id === groupId);
|
||||
if (!groups.length) {
|
||||
throw new Error(`group with id ${groupId} not found`);
|
||||
}
|
||||
const rights = await this.rightRepository.getBy((x) => x.id === rightId);
|
||||
if (!rights.length) {
|
||||
throw new Error(`right with id ${rightId} not found`);
|
||||
}
|
||||
|
||||
return [groups[0], rights[0]];
|
||||
}
|
||||
}
|
||||
10
src/services/index.ts
Normal file
10
src/services/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export * from "./account.factory.service";
|
||||
export * from "./account.service";
|
||||
export * from "./group.factory.service";
|
||||
export * from "./group.service";
|
||||
export * from "./login.service";
|
||||
export * from "./organization.factory.service";
|
||||
export * from "./organization.service";
|
||||
export * from "./registration.service";
|
||||
export * from "./right.factory.service";
|
||||
export * from "./right.service";
|
||||
52
src/services/login.service.ts
Normal file
52
src/services/login.service.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { Inject, Injectable } from "@nestjs/common";
|
||||
import * as contracts from "src/contracts";
|
||||
import { AccountService } from "./account.service";
|
||||
|
||||
@Injectable()
|
||||
export class LoginService {
|
||||
constructor(
|
||||
@Inject("@apihub24/token_service")
|
||||
private readonly tokenService: contracts.TokenService,
|
||||
@Inject("@apihub24/password_service")
|
||||
private readonly passwordService: contracts.PasswordService,
|
||||
@Inject("@apihub24/session_service")
|
||||
private readonly sessionService: contracts.SessionService,
|
||||
@Inject(AccountService)
|
||||
private readonly accountService: AccountService
|
||||
) {}
|
||||
|
||||
async signIn(signIn: contracts.SignIn): Promise<string> {
|
||||
const accounts = await this.accountService.getBy(
|
||||
(x) =>
|
||||
x.accountName === signIn.accountName &&
|
||||
x.active &&
|
||||
!!x.passwordHash?.length
|
||||
);
|
||||
if (
|
||||
!accounts.length ||
|
||||
!(await this.passwordService.verify(
|
||||
signIn.password,
|
||||
accounts[0].passwordHash
|
||||
))
|
||||
) {
|
||||
return "";
|
||||
}
|
||||
const session = await this.sessionService.create(accounts[0]);
|
||||
const token = await this.tokenService.generate(session);
|
||||
return token;
|
||||
}
|
||||
|
||||
async signOut(accountId: string): Promise<void> {
|
||||
const accounts = await this.accountService.getBy((x) => x.id === accountId);
|
||||
if (!accounts.length) {
|
||||
return;
|
||||
}
|
||||
const sessions = await this.sessionService.getBy(
|
||||
(x) => x.id === accounts[0].id
|
||||
);
|
||||
if (!sessions.length) {
|
||||
return;
|
||||
}
|
||||
await this.sessionService.remove(sessions[0].id);
|
||||
}
|
||||
}
|
||||
11
src/services/organization.factory.service.ts
Normal file
11
src/services/organization.factory.service.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Organization } from '../contracts/models/organization';
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationFactoryService {
|
||||
createFromName(name: string): Organization {
|
||||
const organization = new Organization();
|
||||
organization.name = name;
|
||||
return organization;
|
||||
}
|
||||
}
|
||||
33
src/services/organization.service.ts
Normal file
33
src/services/organization.service.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as repository from '@apihub24/repository';
|
||||
import { Organization } from '../contracts/models/organization';
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationService {
|
||||
constructor(
|
||||
@Inject('@apihub24/organization_repository')
|
||||
private readonly organizationRepository: repository.Repository<Organization>,
|
||||
) {}
|
||||
|
||||
async getBy(
|
||||
filter: (organization: Organization) => boolean,
|
||||
): Promise<Organization[]> {
|
||||
return await this.organizationRepository.getBy(filter);
|
||||
}
|
||||
|
||||
async save(organization: Organization): Promise<Organization> {
|
||||
const savedOrganizations = await this.organizationRepository.save([
|
||||
organization,
|
||||
]);
|
||||
if (!savedOrganizations?.length) {
|
||||
throw new Error(`organization (${organization.name}) not saved`);
|
||||
}
|
||||
return savedOrganizations[0];
|
||||
}
|
||||
|
||||
async deleteBy(
|
||||
filter: (organization: Organization) => boolean,
|
||||
): Promise<boolean> {
|
||||
return await this.organizationRepository.deleteBy(filter);
|
||||
}
|
||||
}
|
||||
68
src/services/registration.service.ts
Normal file
68
src/services/registration.service.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { Inject, Injectable } from "@nestjs/common";
|
||||
import { Account } from "../contracts/models/account";
|
||||
import { Registration } from "../contracts/models/registration";
|
||||
import * as mailVerificationService from "../contracts/services/mail.verification.service";
|
||||
import { AccountService } from "./account.service";
|
||||
import { AccountFactoryService } from "./account.factory.service";
|
||||
|
||||
@Injectable()
|
||||
export class RegistrationService {
|
||||
constructor(
|
||||
@Inject(AccountService)
|
||||
private readonly accountService: AccountService,
|
||||
@Inject(AccountFactoryService)
|
||||
private readonly accountFactory: AccountFactoryService,
|
||||
@Inject("@apihub24/mail_verification_service")
|
||||
private readonly mailVerificationService: mailVerificationService.MailVerificationService
|
||||
) {}
|
||||
|
||||
async registerAccount(registration: Registration): Promise<Account | null> {
|
||||
const newAccount = await this.accountFactory.createFromRegistration(
|
||||
registration
|
||||
);
|
||||
return this.accountService.save(newAccount);
|
||||
}
|
||||
|
||||
async verify(email: string, code: string): Promise<boolean> {
|
||||
const verified = await this.mailVerificationService.verify(email, code);
|
||||
if (!verified) {
|
||||
return false;
|
||||
}
|
||||
const unverifiedAccounts = await this.accountService.getBy(
|
||||
(x) => x.email === email && !x.emailVerified
|
||||
);
|
||||
for (const account of unverifiedAccounts) {
|
||||
account.emailVerified = true;
|
||||
await this.accountService.save(account);
|
||||
}
|
||||
return verified;
|
||||
}
|
||||
|
||||
async activateAccount(accountId: string): Promise<boolean> {
|
||||
const accounts = await this.accountService.getBy((x) => x.id === accountId);
|
||||
if (!accounts?.length) {
|
||||
return false;
|
||||
}
|
||||
accounts[0].active = true;
|
||||
const savedAccount = await this.accountService.save(accounts[0]);
|
||||
return savedAccount?.active === true;
|
||||
}
|
||||
|
||||
async deactivateAccount(accountId: string): Promise<boolean> {
|
||||
const accounts = await this.accountService.getBy((x) => x.id === accountId);
|
||||
if (!accounts?.length) {
|
||||
return true;
|
||||
}
|
||||
accounts[0].active = false;
|
||||
const savedAccount = await this.accountService.save(accounts[0]);
|
||||
return savedAccount?.active === false;
|
||||
}
|
||||
|
||||
async unregisterAccount(accountId: string): Promise<void> {
|
||||
const accounts = await this.accountService.getBy((x) => x.id === accountId);
|
||||
if (!accounts?.length) {
|
||||
return;
|
||||
}
|
||||
void this.accountService.delete((x) => x.id === accountId);
|
||||
}
|
||||
}
|
||||
16
src/services/right.factory.service.ts
Normal file
16
src/services/right.factory.service.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { Right } from "../contracts/models/right";
|
||||
import { validate } from "class-validator";
|
||||
|
||||
@Injectable()
|
||||
export class RightFactoryService {
|
||||
async createRight(name: string): Promise<Right> {
|
||||
const right = new Right();
|
||||
right.name = name;
|
||||
const validationErrors = await validate(right);
|
||||
if (validationErrors?.length) {
|
||||
throw new Error(validationErrors[0].toString());
|
||||
}
|
||||
return right;
|
||||
}
|
||||
}
|
||||
22
src/services/right.service.ts
Normal file
22
src/services/right.service.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { Right } from '../contracts/models/right';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as repository from '@apihub24/repository';
|
||||
|
||||
@Injectable()
|
||||
export class RightService {
|
||||
constructor(
|
||||
@Inject('@apihub24/right_repository')
|
||||
private readonly rightRepository: repository.Repository<Right>,
|
||||
) {}
|
||||
|
||||
async getBy(filter: (right: Right) => boolean): Promise<Right[]> {
|
||||
return await this.rightRepository.getBy(filter);
|
||||
}
|
||||
async save(right: Right): Promise<Right | null> {
|
||||
const rights = await this.rightRepository.save([right]);
|
||||
return rights?.length ? rights[0] : null;
|
||||
}
|
||||
async delete(filter: (right: Right) => boolean): Promise<boolean> {
|
||||
return await this.rightRepository.deleteBy(filter);
|
||||
}
|
||||
}
|
||||
43
src/token-authentication.module.ts
Normal file
43
src/token-authentication.module.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { DynamicModule, Module, Provider } from "@nestjs/common";
|
||||
import { RightService } from "./services/right.service";
|
||||
import { GroupService } from "./services/group.service";
|
||||
import { AccountService } from "./services/account.service";
|
||||
import { LoginService } from "./services/login.service";
|
||||
import { RegistrationService } from "./services/registration.service";
|
||||
import { AccountFactoryService } from "./services/account.factory.service";
|
||||
import { ConfigModule } from "@nestjs/config";
|
||||
import { GroupFactoryService } from "./services/group.factory.service";
|
||||
import { RightFactoryService } from "./services/right.factory.service";
|
||||
import { OrganizationService } from "./services/organization.service";
|
||||
import { OrganizationFactoryService } from "./services/organization.factory.service";
|
||||
|
||||
@Module({})
|
||||
export class TokenAuthenticationModule {
|
||||
static forRoot(providers: Provider[]): DynamicModule {
|
||||
return {
|
||||
module: TokenAuthenticationModule,
|
||||
imports: [ConfigModule.forRoot()],
|
||||
providers: [
|
||||
RightService,
|
||||
GroupService,
|
||||
AccountService,
|
||||
OrganizationService,
|
||||
LoginService,
|
||||
RegistrationService,
|
||||
OrganizationFactoryService,
|
||||
AccountFactoryService,
|
||||
GroupFactoryService,
|
||||
RightFactoryService,
|
||||
...providers,
|
||||
],
|
||||
exports: [
|
||||
RightService,
|
||||
GroupService,
|
||||
AccountService,
|
||||
LoginService,
|
||||
RegistrationService,
|
||||
AccountFactoryService,
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
26
tsconfig.json
Normal file
26
tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext",
|
||||
"resolvePackageJsonExports": true,
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "ES2023",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitAny": false,
|
||||
"strictBindCallApply": false,
|
||||
"noFallthroughCasesInSwitch": false
|
||||
},
|
||||
"include": ["./src"]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user