wip
This commit is contained in:
parent
3700deec8b
commit
e7d7f47b53
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
|||||||
.vscode
|
.vscode
|
||||||
.idea
|
.idea
|
||||||
dist
|
dist
|
||||||
|
coverage
|
||||||
node_modules
|
node_modules
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
.vscode
|
.vscode
|
||||||
.idea
|
.idea
|
||||||
|
coverage
|
||||||
node_modules
|
node_modules
|
||||||
src
|
src
|
||||||
.gitignore
|
.gitignore
|
||||||
|
|||||||
56
README.md
Normal file
56
README.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Jwt Generator
|
||||||
|
|
||||||
|
A NestJs Library to generate JWTs.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm i --save @apihub24/jwt-generator
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
| Dependency | Description |
|
||||||
|
| ------------------------- | ---------------------------------------------------------- |
|
||||||
|
| @nestjs/config | use the NestJs Config Module to get some keys listed below |
|
||||||
|
| @apihub24/session_service | used to create new Sessions when a new Token was created |
|
||||||
|
| JWT_SECRET | NestJs Config Key for Secret used to Sign JWT |
|
||||||
|
| JWT_ISSUER | NestJs Config Key for the JWT Issuer |
|
||||||
|
| JWT_AUDIENCE | NestJs Config Key for the JWT Audience |
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import {
|
||||||
|
JwtGeneratorModule,
|
||||||
|
JwtService,
|
||||||
|
APIHUB24_TOKEN_SERVICE_INJECTION_KEY,
|
||||||
|
} from "@apihub24/jwt-generator";
|
||||||
|
// we use the @apihub24/session_service implementation from the InMemorySessionsModule
|
||||||
|
import { InMemorySessionsModule } from "@apihub24/in-memory-sessions";
|
||||||
|
// use the takeExportedProviders function from NestJs Helper Module to select the exported Providers
|
||||||
|
import { takeExportedProviders } from "@apihub24/nestjs-helper";
|
||||||
|
|
||||||
|
// use JwtGeneratorModule
|
||||||
|
const inMemorySessionsModule = InMemorySessionsModule.forRoot();
|
||||||
|
const jwtGeneratorModule = JwtGeneratorModule.forRoot([
|
||||||
|
...takeExportedProviders(inMemorySessionsModule),
|
||||||
|
]);
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [jwtGeneratorModule],
|
||||||
|
})
|
||||||
|
|
||||||
|
// now you can inject the service with injection Token
|
||||||
|
import * as tokenAuthentication from "@apihub24/token-authentication";
|
||||||
|
|
||||||
|
export class Test {
|
||||||
|
constructor(
|
||||||
|
@Inject(APIHUB24_TOKEN_SERVICE_INJECTION_KEY) jwtGenerator: JwtService
|
||||||
|
);
|
||||||
|
|
||||||
|
async test(session: tokenAuthentication.ISession) {
|
||||||
|
const token = await this.jwtGenerator.generate(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
4662
package-lock.json
generated
4662
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
38
package.json
38
package.json
@ -1,20 +1,29 @@
|
|||||||
{
|
{
|
||||||
"name": "@apihub24/jwt-generator",
|
"name": "@apihub24/jwt-generator",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc"
|
"build": "rimraf ./dist && tsc",
|
||||||
|
"test": "jest",
|
||||||
|
"test:coverage": "jest --coverage",
|
||||||
|
"pub": "npm publish",
|
||||||
|
"rel": "npm i && npm run build && npm run test:coverage"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/common": "^11.1.6",
|
"@nestjs/common": "^11.1.6",
|
||||||
"@nestjs/config": "^4.0.2",
|
"@nestjs/config": "^4.0.2",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"@apihub24/token-authentication": "^1.0.0"
|
"@apihub24/token-authentication": "^1.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.9.2"
|
"rimraf": "^6.0.1",
|
||||||
|
"@nestjs/testing": "^11.1.6",
|
||||||
|
"typescript": "^5.9.2",
|
||||||
|
"@types/jest": "^30.0.0",
|
||||||
|
"jest": "^30.0.0",
|
||||||
|
"ts-jest": "^29.4.1"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": {
|
"author": {
|
||||||
@ -25,5 +34,26 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"moduleFileExtensions": [
|
||||||
|
"js",
|
||||||
|
"json",
|
||||||
|
"ts"
|
||||||
|
],
|
||||||
|
"rootDir": ".",
|
||||||
|
"testRegex": ".*\\.spec\\.ts$",
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
|
},
|
||||||
|
"collectCoverageFrom": [
|
||||||
|
"**/*.(t|j)s"
|
||||||
|
],
|
||||||
|
"coverageDirectory": "./coverage",
|
||||||
|
"testEnvironment": "node",
|
||||||
|
"roots": [
|
||||||
|
"<rootDir>/src/"
|
||||||
|
],
|
||||||
|
"moduleNameMapper": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
34
src/jwt.generator.module.spec.ts
Normal file
34
src/jwt.generator.module.spec.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { Test, TestingModule } from "@nestjs/testing";
|
||||||
|
import {
|
||||||
|
APIHUB24_TOKEN_SERVICE_INJECTION_KEY,
|
||||||
|
JwtGeneratorModule,
|
||||||
|
JwtService,
|
||||||
|
} from "./";
|
||||||
|
import { SessionServiceMock } from "../test/session.service.mock";
|
||||||
|
|
||||||
|
describe("JwtGeneratorModule Tests", () => {
|
||||||
|
let module: TestingModule | null = null;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
module = await Test.createTestingModule({
|
||||||
|
imports: [
|
||||||
|
JwtGeneratorModule.forRoot([
|
||||||
|
{
|
||||||
|
provide: "@apihub24/session_service",
|
||||||
|
useClass: SessionServiceMock,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should export injection Key @apihub24/token_service", () => {
|
||||||
|
expect(APIHUB24_TOKEN_SERVICE_INJECTION_KEY).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should get JwtService by injection Key", () => {
|
||||||
|
expect(module).toBeDefined();
|
||||||
|
const service = module?.get("@apihub24/token_service");
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,13 +1,15 @@
|
|||||||
import { DynamicModule, Module, Provider } from '@nestjs/common';
|
import { DynamicModule, Module, Provider } from "@nestjs/common";
|
||||||
import { JwtService } from './jwt.service';
|
import { JwtService } from "./jwt.service";
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from "@nestjs/config";
|
||||||
|
|
||||||
|
export const APIHUB24_TOKEN_SERVICE_INJECTION_KEY = "@apihub24/token_service";
|
||||||
|
|
||||||
@Module({})
|
@Module({})
|
||||||
export class JwtGeneratorModule {
|
export class JwtGeneratorModule {
|
||||||
static forRoot(requiredProviders: Provider[]): DynamicModule {
|
static forRoot(requiredProviders: Provider[]): DynamicModule {
|
||||||
const providers = [
|
const providers = [
|
||||||
{
|
{
|
||||||
provide: '@apihub24/token_service',
|
provide: APIHUB24_TOKEN_SERVICE_INJECTION_KEY,
|
||||||
useClass: JwtService,
|
useClass: JwtService,
|
||||||
},
|
},
|
||||||
...requiredProviders,
|
...requiredProviders,
|
||||||
|
|||||||
169
src/jwt.service.spec.ts
Normal file
169
src/jwt.service.spec.ts
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
import { ISession } from "@apihub24/token-authentication";
|
||||||
|
import { APIHUB24_TOKEN_SERVICE_INJECTION_KEY, JwtGeneratorModule } from "./";
|
||||||
|
import { Test, TestingModule } from "@nestjs/testing";
|
||||||
|
import { SessionServiceMock } from "../test/session.service.mock";
|
||||||
|
|
||||||
|
describe("JwtService Tests", () => {
|
||||||
|
let module: TestingModule | null = null;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
module = await Test.createTestingModule({
|
||||||
|
imports: [
|
||||||
|
JwtGeneratorModule.forRoot([
|
||||||
|
{
|
||||||
|
provide: "@apihub24/session_service",
|
||||||
|
useClass: SessionServiceMock,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should generate JWT", async () => {
|
||||||
|
expect(module).toBeDefined();
|
||||||
|
const service = module?.get(APIHUB24_TOKEN_SERVICE_INJECTION_KEY);
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
|
||||||
|
const session: ISession = {
|
||||||
|
id: "1",
|
||||||
|
account: {
|
||||||
|
id: "1",
|
||||||
|
accountName: "MockAccount",
|
||||||
|
email: "MockAccount@example.de",
|
||||||
|
passwordHash: "hash",
|
||||||
|
emailVerified: true,
|
||||||
|
active: true,
|
||||||
|
groups: [],
|
||||||
|
},
|
||||||
|
metaData: {},
|
||||||
|
};
|
||||||
|
const token = await service?.generate(session, session.account.accountName);
|
||||||
|
expect(token).toBeDefined();
|
||||||
|
expect(token?.length).toBeGreaterThan(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should verify valid JWT", async () => {
|
||||||
|
expect(module).toBeDefined();
|
||||||
|
const service = module?.get(APIHUB24_TOKEN_SERVICE_INJECTION_KEY);
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
|
||||||
|
const session: ISession = {
|
||||||
|
id: "1",
|
||||||
|
account: {
|
||||||
|
id: "1",
|
||||||
|
accountName: "MockAccount",
|
||||||
|
email: "MockAccount@example.de",
|
||||||
|
passwordHash: "hash",
|
||||||
|
emailVerified: true,
|
||||||
|
active: true,
|
||||||
|
groups: [],
|
||||||
|
},
|
||||||
|
metaData: {},
|
||||||
|
};
|
||||||
|
const token = await service?.generate(session, session.account.accountName);
|
||||||
|
expect(token).toBeDefined();
|
||||||
|
expect(token?.length).toBeGreaterThan(3);
|
||||||
|
expect(await service?.validate(token ?? "")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not verify invalid JWT", async () => {
|
||||||
|
expect(module).toBeDefined();
|
||||||
|
const service = module?.get(APIHUB24_TOKEN_SERVICE_INJECTION_KEY);
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
expect(await service?.validate("")).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should get Account from JWT", async () => {
|
||||||
|
expect(module).toBeDefined();
|
||||||
|
const service = module?.get(APIHUB24_TOKEN_SERVICE_INJECTION_KEY);
|
||||||
|
const sessionService = module?.get("@apihub24/session_service");
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
|
||||||
|
const session: ISession = {
|
||||||
|
id: "1",
|
||||||
|
account: {
|
||||||
|
id: "1",
|
||||||
|
accountName: "MockAccount",
|
||||||
|
email: "MockAccount@example.de",
|
||||||
|
passwordHash: "hash",
|
||||||
|
emailVerified: true,
|
||||||
|
active: true,
|
||||||
|
groups: [],
|
||||||
|
},
|
||||||
|
metaData: {},
|
||||||
|
};
|
||||||
|
jest.spyOn(sessionService, "getById").mockReturnValue(session);
|
||||||
|
const token = await service?.generate(session, session.account.accountName);
|
||||||
|
expect(token).toBeDefined();
|
||||||
|
expect(token?.length).toBeGreaterThan(3);
|
||||||
|
const account = await service?.getAccount(token ?? "");
|
||||||
|
expect(account).toBeDefined();
|
||||||
|
expect(account?.id).toBe(session.account.id);
|
||||||
|
expect(account?.accountName).toBe(session.account.accountName);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not get Account from invalid JWT", async () => {
|
||||||
|
expect(module).toBeDefined();
|
||||||
|
const service = module?.get(APIHUB24_TOKEN_SERVICE_INJECTION_KEY);
|
||||||
|
const sessionService = module?.get("@apihub24/session_service");
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
|
||||||
|
expect(await service?.getAccount("")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not get Account if no Session available", async () => {
|
||||||
|
expect(module).toBeDefined();
|
||||||
|
const service = module?.get(APIHUB24_TOKEN_SERVICE_INJECTION_KEY);
|
||||||
|
const sessionService = module?.get("@apihub24/session_service");
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
|
||||||
|
const session: ISession = {
|
||||||
|
id: "1",
|
||||||
|
account: {
|
||||||
|
id: "1",
|
||||||
|
accountName: "MockAccount",
|
||||||
|
email: "MockAccount@example.de",
|
||||||
|
passwordHash: "hash",
|
||||||
|
emailVerified: true,
|
||||||
|
active: true,
|
||||||
|
groups: [],
|
||||||
|
},
|
||||||
|
metaData: {},
|
||||||
|
};
|
||||||
|
jest.spyOn(sessionService, "getById").mockReturnValue(null);
|
||||||
|
const token = await service?.generate(session, session.account.accountName);
|
||||||
|
expect(token).toBeDefined();
|
||||||
|
expect(token?.length).toBeGreaterThan(3);
|
||||||
|
expect(await service?.getAccount(token ?? "")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not get Account if no Account in Session available", async () => {
|
||||||
|
expect(module).toBeDefined();
|
||||||
|
const service = module?.get(APIHUB24_TOKEN_SERVICE_INJECTION_KEY);
|
||||||
|
const sessionService = module?.get("@apihub24/session_service");
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
|
||||||
|
const session: ISession = {
|
||||||
|
id: "1",
|
||||||
|
account: {
|
||||||
|
id: "1",
|
||||||
|
accountName: "MockAccount",
|
||||||
|
email: "MockAccount@example.de",
|
||||||
|
passwordHash: "hash",
|
||||||
|
emailVerified: true,
|
||||||
|
active: true,
|
||||||
|
groups: [],
|
||||||
|
},
|
||||||
|
metaData: {},
|
||||||
|
};
|
||||||
|
jest.spyOn(sessionService, "getById").mockReturnValue({
|
||||||
|
id: "1",
|
||||||
|
account: null,
|
||||||
|
metaData: {},
|
||||||
|
});
|
||||||
|
const token = await service?.generate(session, session.account.accountName);
|
||||||
|
expect(token).toBeDefined();
|
||||||
|
expect(token?.length).toBeGreaterThan(3);
|
||||||
|
expect(await service?.getAccount(token ?? "")).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -4,15 +4,20 @@ import { ConfigService } from "@nestjs/config";
|
|||||||
import jwt, { JwtPayload } from "jsonwebtoken";
|
import jwt, { JwtPayload } from "jsonwebtoken";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JwtService implements tokenAuthentication.TokenService {
|
export class JwtService implements tokenAuthentication.ITokenService {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(ConfigService)
|
@Inject(ConfigService)
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
@Inject("@apihub24/session_service")
|
@Inject("@apihub24/session_service")
|
||||||
private readonly sessionService: tokenAuthentication.SessionService
|
private readonly sessionService: tokenAuthentication.ISessionService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
generate(session: tokenAuthentication.Session): Promise<string> {
|
generate(
|
||||||
|
session: tokenAuthentication.ISession,
|
||||||
|
subject: string,
|
||||||
|
expires?: string,
|
||||||
|
algorithm?: tokenAuthentication.Algorithm
|
||||||
|
): Promise<string> {
|
||||||
return Promise.resolve(
|
return Promise.resolve(
|
||||||
jwt.sign(
|
jwt.sign(
|
||||||
{
|
{
|
||||||
@ -20,7 +25,13 @@ export class JwtService implements tokenAuthentication.TokenService {
|
|||||||
accountId: session.account.id,
|
accountId: session.account.id,
|
||||||
},
|
},
|
||||||
this.getSecretKey(),
|
this.getSecretKey(),
|
||||||
{ expiresIn: "1h" }
|
{
|
||||||
|
expiresIn: expires ?? "1h",
|
||||||
|
algorithm: algorithm ?? "HS512",
|
||||||
|
subject,
|
||||||
|
issuer: this.getIssuer(),
|
||||||
|
audience: this.getAudience(),
|
||||||
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -35,7 +46,9 @@ export class JwtService implements tokenAuthentication.TokenService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAccount(token: string): Promise<tokenAuthentication.Account | null> {
|
async getAccount(
|
||||||
|
token: string
|
||||||
|
): Promise<tokenAuthentication.IAccount | null> {
|
||||||
try {
|
try {
|
||||||
const payload = jwt.verify(token, this.getSecretKey()) as JwtPayload;
|
const payload = jwt.verify(token, this.getSecretKey()) as JwtPayload;
|
||||||
if (typeof payload?.sessionId !== "string") {
|
if (typeof payload?.sessionId !== "string") {
|
||||||
@ -58,4 +71,12 @@ export class JwtService implements tokenAuthentication.TokenService {
|
|||||||
private getSecretKey(): string {
|
private getSecretKey(): string {
|
||||||
return this.configService.get<string>("JWT_SECRET", "thisisnotsigrid");
|
return this.configService.get<string>("JWT_SECRET", "thisisnotsigrid");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getIssuer(): string {
|
||||||
|
return this.configService.get<string>("JWT_ISSUER", "unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAudience(): string {
|
||||||
|
return this.configService.get<string>("JWT_AUDIENCE", "unknown");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
test/session.service.mock.ts
Normal file
20
test/session.service.mock.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import {
|
||||||
|
IAccount,
|
||||||
|
ISession,
|
||||||
|
ISessionService,
|
||||||
|
} from "@apihub24/token-authentication";
|
||||||
|
|
||||||
|
export class SessionServiceMock implements ISessionService {
|
||||||
|
create(account: IAccount): Promise<ISession> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
getBy(filter: (account: IAccount) => boolean): Promise<ISession[]> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
getById(sessionId: string): Promise<ISession | null> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
remove(sessionId: string): Promise<void> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -22,5 +22,6 @@
|
|||||||
"strictBindCallApply": false,
|
"strictBindCallApply": false,
|
||||||
"noFallthroughCasesInSwitch": false
|
"noFallthroughCasesInSwitch": false
|
||||||
},
|
},
|
||||||
"include": ["./src"]
|
"include": ["./src"],
|
||||||
|
"exclude": ["**/*.spec.ts", "**/*.mock.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user