mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-10 01:16:39 +02:00
refactor: add OpenAPI schema to simple-password-provider controller (#1734)
* refactor: add OpenAPI schema to simple-password-provider controller * finish implementation after merge * refactor: address PR comments
This commit is contained in:
parent
89d25c8634
commit
a792594e98
@ -23,6 +23,7 @@ import { healthCheckSchema } from './spec/health-check-schema';
|
|||||||
import { healthOverviewSchema } from './spec/health-overview-schema';
|
import { healthOverviewSchema } from './spec/health-overview-schema';
|
||||||
import { healthReportSchema } from './spec/health-report-schema';
|
import { healthReportSchema } from './spec/health-report-schema';
|
||||||
import { legalValueSchema } from './spec/legal-value-schema';
|
import { legalValueSchema } from './spec/legal-value-schema';
|
||||||
|
import { loginSchema } from './spec/login-schema';
|
||||||
import { idSchema } from './spec/id-schema';
|
import { idSchema } from './spec/id-schema';
|
||||||
import { mapValues } from '../util/map-values';
|
import { mapValues } from '../util/map-values';
|
||||||
import { nameSchema } from './spec/name-schema';
|
import { nameSchema } from './spec/name-schema';
|
||||||
@ -116,6 +117,7 @@ export const schemas = {
|
|||||||
healthOverviewSchema,
|
healthOverviewSchema,
|
||||||
healthReportSchema,
|
healthReportSchema,
|
||||||
legalValueSchema,
|
legalValueSchema,
|
||||||
|
loginSchema,
|
||||||
nameSchema,
|
nameSchema,
|
||||||
idSchema,
|
idSchema,
|
||||||
meSchema,
|
meSchema,
|
||||||
@ -172,16 +174,12 @@ export interface JsonSchemaProps {
|
|||||||
components: object;
|
components: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ApiOperation<Tag = 'client' | 'admin' | 'other'>
|
export interface ApiOperation<Tag = 'admin' | 'client' | 'auth' | 'other'>
|
||||||
extends Omit<OpenAPIV3.OperationObject, 'tags'> {
|
extends Omit<OpenAPIV3.OperationObject, 'tags'> {
|
||||||
operationId: string;
|
operationId: string;
|
||||||
tags: [Tag];
|
tags: [Tag];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AdminApiOperation = ApiOperation<'admin'>;
|
|
||||||
export type ClientApiOperation = ApiOperation<'client'>;
|
|
||||||
export type OtherApiOperation = ApiOperation<'other'>;
|
|
||||||
|
|
||||||
export const createRequestSchema = (
|
export const createRequestSchema = (
|
||||||
schemaName: string,
|
schemaName: string,
|
||||||
): OpenAPIV3.RequestBodyObject => {
|
): OpenAPIV3.RequestBodyObject => {
|
||||||
|
19
src/lib/openapi/spec/login-schema.ts
Normal file
19
src/lib/openapi/spec/login-schema.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { FromSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
|
export const loginSchema = {
|
||||||
|
$id: '#/components/schemas/loginSchema',
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
required: ['username', 'password'],
|
||||||
|
properties: {
|
||||||
|
username: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type LoginSchema = FromSchema<typeof loginSchema>;
|
@ -55,7 +55,7 @@ class ResetPasswordController extends Controller {
|
|||||||
permission: NONE,
|
permission: NONE,
|
||||||
middleware: [
|
middleware: [
|
||||||
openApiService.validPath({
|
openApiService.validPath({
|
||||||
tags: ['other'],
|
tags: ['auth'],
|
||||||
operationId: 'validateToken',
|
operationId: 'validateToken',
|
||||||
responses: { 200: createResponseSchema('tokenUserSchema') },
|
responses: { 200: createResponseSchema('tokenUserSchema') },
|
||||||
}),
|
}),
|
||||||
|
@ -1,18 +1,24 @@
|
|||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import User from '../../types/user';
|
import User from '../../types/user';
|
||||||
import PasswordProvider from './simple-password-provider';
|
import { SimplePasswordProvider } from './simple-password-provider';
|
||||||
import PasswordMismatchError from '../../error/password-mismatch';
|
import PasswordMismatchError from '../../error/password-mismatch';
|
||||||
import getLogger from '../../../test/fixtures/no-logger';
|
import { createTestConfig } from '../../../test/config/test-config';
|
||||||
|
import { OpenApiService } from '../../services/openapi-service';
|
||||||
|
|
||||||
test('Should require password', async () => {
|
test('Should require password', async () => {
|
||||||
|
const config = createTestConfig();
|
||||||
|
const openApiService = new OpenApiService(config);
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
const userService = () => {};
|
const userService = () => {};
|
||||||
// @ts-ignore
|
|
||||||
const ctr = new PasswordProvider({ getLogger }, { userService });
|
|
||||||
|
|
||||||
//@ts-ignore
|
const ctr = new SimplePasswordProvider(config, {
|
||||||
|
// @ts-expect-error
|
||||||
|
userService,
|
||||||
|
openApiService,
|
||||||
|
});
|
||||||
|
|
||||||
app.use('/auth/simple', ctr.router);
|
app.use('/auth/simple', ctr.router);
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
@ -23,6 +29,8 @@ test('Should require password', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Should login user', async () => {
|
test('Should login user', async () => {
|
||||||
|
const config = createTestConfig();
|
||||||
|
const openApiService = new OpenApiService(config);
|
||||||
const username = 'ola';
|
const username = 'ola';
|
||||||
const password = 'simplepass';
|
const password = 'simplepass';
|
||||||
const user = new User({ id: 123, username });
|
const user = new User({ id: 123, username });
|
||||||
@ -30,10 +38,11 @@ test('Should login user', async () => {
|
|||||||
const app = express();
|
const app = express();
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
//@ts-ignore
|
// @ts-expect-error
|
||||||
req.session = {};
|
req.session = {};
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
const userService = {
|
const userService = {
|
||||||
loginUser: (u, p) => {
|
loginUser: (u, p) => {
|
||||||
if (u === username && p === password) {
|
if (u === username && p === password) {
|
||||||
@ -42,10 +51,13 @@ test('Should login user', async () => {
|
|||||||
throw new Error('Wrong password');
|
throw new Error('Wrong password');
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// @ts-ignore
|
|
||||||
const ctr = new PasswordProvider({ getLogger }, { userService });
|
|
||||||
|
|
||||||
//@ts-ignore
|
const ctr = new SimplePasswordProvider(config, {
|
||||||
|
// @ts-expect-error
|
||||||
|
userService,
|
||||||
|
openApiService,
|
||||||
|
});
|
||||||
|
|
||||||
app.use('/auth/simple', ctr.router);
|
app.use('/auth/simple', ctr.router);
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
@ -57,6 +69,8 @@ test('Should login user', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Should not login user with wrong password', async () => {
|
test('Should not login user with wrong password', async () => {
|
||||||
|
const config = createTestConfig();
|
||||||
|
const openApiService = new OpenApiService(config);
|
||||||
const username = 'ola';
|
const username = 'ola';
|
||||||
const password = 'simplepass';
|
const password = 'simplepass';
|
||||||
const user = new User({ id: 133, username });
|
const user = new User({ id: 133, username });
|
||||||
@ -64,10 +78,11 @@ test('Should not login user with wrong password', async () => {
|
|||||||
const app = express();
|
const app = express();
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
//@ts-ignore
|
// @ts-expect-error
|
||||||
req.session = {};
|
req.session = {};
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
const userService = {
|
const userService = {
|
||||||
loginUser: (u, p) => {
|
loginUser: (u, p) => {
|
||||||
if (u === username && p === password) {
|
if (u === username && p === password) {
|
||||||
@ -76,10 +91,13 @@ test('Should not login user with wrong password', async () => {
|
|||||||
throw new PasswordMismatchError();
|
throw new PasswordMismatchError();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// @ts-ignore
|
|
||||||
const ctr = new PasswordProvider({ getLogger }, { userService });
|
|
||||||
|
|
||||||
//@ts-ignore
|
const ctr = new SimplePasswordProvider(config, {
|
||||||
|
// @ts-expect-error
|
||||||
|
userService,
|
||||||
|
openApiService,
|
||||||
|
});
|
||||||
|
|
||||||
app.use('/auth/simple', ctr.router);
|
app.use('/auth/simple', ctr.router);
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
|
import { OpenApiService } from '../../services/openapi-service';
|
||||||
import { Logger } from '../../logger';
|
import { Logger } from '../../logger';
|
||||||
import { IUnleashConfig } from '../../server-impl';
|
import { IUnleashConfig } from '../../server-impl';
|
||||||
import UserService from '../../services/user-service';
|
import UserService from '../../services/user-service';
|
||||||
@ -6,37 +7,61 @@ import { IUnleashServices } from '../../types';
|
|||||||
import { NONE } from '../../types/permissions';
|
import { NONE } from '../../types/permissions';
|
||||||
import Controller from '../controller';
|
import Controller from '../controller';
|
||||||
import { IAuthRequest } from '../unleash-types';
|
import { IAuthRequest } from '../unleash-types';
|
||||||
|
import { createRequestSchema, createResponseSchema } from '../../openapi';
|
||||||
|
import { userSchema, UserSchema } from '../../openapi/spec/user-schema';
|
||||||
|
import { LoginSchema } from '../../openapi/spec/login-schema';
|
||||||
|
import { serializeDates } from '../../types/serialize-dates';
|
||||||
|
|
||||||
class PasswordProvider extends Controller {
|
export class SimplePasswordProvider extends Controller {
|
||||||
private userService: UserService;
|
|
||||||
|
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
|
private openApiService: OpenApiService;
|
||||||
|
|
||||||
|
private userService: UserService;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
config: IUnleashConfig,
|
config: IUnleashConfig,
|
||||||
{ userService }: Pick<IUnleashServices, 'userService'>,
|
{
|
||||||
|
userService,
|
||||||
|
openApiService,
|
||||||
|
}: Pick<IUnleashServices, 'userService' | 'openApiService'>,
|
||||||
) {
|
) {
|
||||||
super(config);
|
super(config);
|
||||||
this.logger = config.getLogger('/auth/password-provider.js');
|
this.logger = config.getLogger('/auth/password-provider.js');
|
||||||
|
this.openApiService = openApiService;
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
|
|
||||||
this.post('/login', this.login, NONE);
|
this.route({
|
||||||
}
|
method: 'post',
|
||||||
|
path: '/login',
|
||||||
async login(req: IAuthRequest, res: Response): Promise<void> {
|
handler: this.login,
|
||||||
const { username, password } = req.body;
|
permission: NONE,
|
||||||
|
middleware: [
|
||||||
if (!username || !password) {
|
openApiService.validPath({
|
||||||
res.status(400).json({
|
tags: ['auth'],
|
||||||
message: 'You must provide username and password',
|
operationId: 'login',
|
||||||
|
requestBody: createRequestSchema('loginSchema'),
|
||||||
|
responses: {
|
||||||
|
200: createResponseSchema('userSchema'),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async login(
|
||||||
|
req: IAuthRequest<void, void, LoginSchema>,
|
||||||
|
res: Response<UserSchema>,
|
||||||
|
): Promise<void> {
|
||||||
|
const { username, password } = req.body;
|
||||||
|
|
||||||
const user = await this.userService.loginUser(username, password);
|
const user = await this.userService.loginUser(username, password);
|
||||||
req.session.user = user;
|
req.session.user = user;
|
||||||
res.status(200).json(user);
|
this.openApiService.respondWithValidation(
|
||||||
|
200,
|
||||||
|
res,
|
||||||
|
userSchema.$id,
|
||||||
|
serializeDates(user),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PasswordProvider;
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { BackstageController } from './backstage';
|
import { BackstageController } from './backstage';
|
||||||
import ResetPasswordController from './auth/reset-password-controller';
|
import ResetPasswordController from './auth/reset-password-controller';
|
||||||
import SimplePasswordProvider from './auth/simple-password-provider';
|
import { SimplePasswordProvider } from './auth/simple-password-provider';
|
||||||
import { IUnleashConfig } from '../types/option';
|
import { IUnleashConfig } from '../types/option';
|
||||||
import { IUnleashServices } from '../types/services';
|
import { IUnleashServices } from '../types/services';
|
||||||
import { api } from './api-def';
|
import { api } from './api-def';
|
||||||
|
@ -2,9 +2,7 @@ import openapi, { IExpressOpenApi } from '@unleash/express-openapi';
|
|||||||
import { Express, RequestHandler, Response } from 'express';
|
import { Express, RequestHandler, Response } from 'express';
|
||||||
import { IUnleashConfig } from '../types/option';
|
import { IUnleashConfig } from '../types/option';
|
||||||
import {
|
import {
|
||||||
AdminApiOperation,
|
ApiOperation,
|
||||||
ClientApiOperation,
|
|
||||||
OtherApiOperation,
|
|
||||||
createOpenApiSchema,
|
createOpenApiSchema,
|
||||||
JsonSchemaProps,
|
JsonSchemaProps,
|
||||||
removeJsonSchemaProps,
|
removeJsonSchemaProps,
|
||||||
@ -31,9 +29,7 @@ export class OpenApiService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
validPath(
|
validPath(op: ApiOperation): RequestHandler {
|
||||||
op: AdminApiOperation | ClientApiOperation | OtherApiOperation,
|
|
||||||
): RequestHandler {
|
|
||||||
return this.api.validPath(op);
|
return this.api.validPath(op);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1041,6 +1041,22 @@ Object {
|
|||||||
],
|
],
|
||||||
"type": "object",
|
"type": "object",
|
||||||
},
|
},
|
||||||
|
"loginSchema": Object {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": Object {
|
||||||
|
"password": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"username": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": Array [
|
||||||
|
"username",
|
||||||
|
"password",
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
"meSchema": Object {
|
"meSchema": Object {
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
@ -4950,7 +4966,7 @@ Object {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"tags": Array [
|
"tags": Array [
|
||||||
"other",
|
"auth",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -4978,6 +4994,37 @@ Object {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"/auth/simple/login": Object {
|
||||||
|
"post": Object {
|
||||||
|
"operationId": "login",
|
||||||
|
"requestBody": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/loginSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "loginSchema",
|
||||||
|
"required": true,
|
||||||
|
},
|
||||||
|
"responses": Object {
|
||||||
|
"200": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/userSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "userSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tags": Array [
|
||||||
|
"auth",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
"/health": Object {
|
"/health": Object {
|
||||||
"get": Object {
|
"get": Object {
|
||||||
"operationId": "getHealth",
|
"operationId": "getHealth",
|
||||||
|
Loading…
Reference in New Issue
Block a user