mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01: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 { healthReportSchema } from './spec/health-report-schema';
|
||||
import { legalValueSchema } from './spec/legal-value-schema';
|
||||
import { loginSchema } from './spec/login-schema';
|
||||
import { idSchema } from './spec/id-schema';
|
||||
import { mapValues } from '../util/map-values';
|
||||
import { nameSchema } from './spec/name-schema';
|
||||
@ -116,6 +117,7 @@ export const schemas = {
|
||||
healthOverviewSchema,
|
||||
healthReportSchema,
|
||||
legalValueSchema,
|
||||
loginSchema,
|
||||
nameSchema,
|
||||
idSchema,
|
||||
meSchema,
|
||||
@ -172,16 +174,12 @@ export interface JsonSchemaProps {
|
||||
components: object;
|
||||
}
|
||||
|
||||
interface ApiOperation<Tag = 'client' | 'admin' | 'other'>
|
||||
export interface ApiOperation<Tag = 'admin' | 'client' | 'auth' | 'other'>
|
||||
extends Omit<OpenAPIV3.OperationObject, 'tags'> {
|
||||
operationId: string;
|
||||
tags: [Tag];
|
||||
}
|
||||
|
||||
export type AdminApiOperation = ApiOperation<'admin'>;
|
||||
export type ClientApiOperation = ApiOperation<'client'>;
|
||||
export type OtherApiOperation = ApiOperation<'other'>;
|
||||
|
||||
export const createRequestSchema = (
|
||||
schemaName: string,
|
||||
): 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,
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['other'],
|
||||
tags: ['auth'],
|
||||
operationId: 'validateToken',
|
||||
responses: { 200: createResponseSchema('tokenUserSchema') },
|
||||
}),
|
||||
|
@ -1,18 +1,24 @@
|
||||
import request from 'supertest';
|
||||
import express from 'express';
|
||||
import User from '../../types/user';
|
||||
import PasswordProvider from './simple-password-provider';
|
||||
import { SimplePasswordProvider } from './simple-password-provider';
|
||||
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 () => {
|
||||
const config = createTestConfig();
|
||||
const openApiService = new OpenApiService(config);
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
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);
|
||||
|
||||
const res = await request(app)
|
||||
@ -23,6 +29,8 @@ test('Should require password', async () => {
|
||||
});
|
||||
|
||||
test('Should login user', async () => {
|
||||
const config = createTestConfig();
|
||||
const openApiService = new OpenApiService(config);
|
||||
const username = 'ola';
|
||||
const password = 'simplepass';
|
||||
const user = new User({ id: 123, username });
|
||||
@ -30,10 +38,11 @@ test('Should login user', async () => {
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use((req, res, next) => {
|
||||
//@ts-ignore
|
||||
// @ts-expect-error
|
||||
req.session = {};
|
||||
next();
|
||||
});
|
||||
|
||||
const userService = {
|
||||
loginUser: (u, p) => {
|
||||
if (u === username && p === password) {
|
||||
@ -42,10 +51,13 @@ test('Should login user', async () => {
|
||||
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);
|
||||
|
||||
const res = await request(app)
|
||||
@ -57,6 +69,8 @@ test('Should login user', async () => {
|
||||
});
|
||||
|
||||
test('Should not login user with wrong password', async () => {
|
||||
const config = createTestConfig();
|
||||
const openApiService = new OpenApiService(config);
|
||||
const username = 'ola';
|
||||
const password = 'simplepass';
|
||||
const user = new User({ id: 133, username });
|
||||
@ -64,10 +78,11 @@ test('Should not login user with wrong password', async () => {
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use((req, res, next) => {
|
||||
//@ts-ignore
|
||||
// @ts-expect-error
|
||||
req.session = {};
|
||||
next();
|
||||
});
|
||||
|
||||
const userService = {
|
||||
loginUser: (u, p) => {
|
||||
if (u === username && p === password) {
|
||||
@ -76,10 +91,13 @@ test('Should not login user with wrong password', async () => {
|
||||
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);
|
||||
|
||||
const res = await request(app)
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Response } from 'express';
|
||||
import { OpenApiService } from '../../services/openapi-service';
|
||||
import { Logger } from '../../logger';
|
||||
import { IUnleashConfig } from '../../server-impl';
|
||||
import UserService from '../../services/user-service';
|
||||
@ -6,37 +7,61 @@ import { IUnleashServices } from '../../types';
|
||||
import { NONE } from '../../types/permissions';
|
||||
import Controller from '../controller';
|
||||
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 {
|
||||
private userService: UserService;
|
||||
|
||||
export class SimplePasswordProvider extends Controller {
|
||||
private logger: Logger;
|
||||
|
||||
private openApiService: OpenApiService;
|
||||
|
||||
private userService: UserService;
|
||||
|
||||
constructor(
|
||||
config: IUnleashConfig,
|
||||
{ userService }: Pick<IUnleashServices, 'userService'>,
|
||||
{
|
||||
userService,
|
||||
openApiService,
|
||||
}: Pick<IUnleashServices, 'userService' | 'openApiService'>,
|
||||
) {
|
||||
super(config);
|
||||
this.logger = config.getLogger('/auth/password-provider.js');
|
||||
this.openApiService = openApiService;
|
||||
this.userService = userService;
|
||||
|
||||
this.post('/login', this.login, NONE);
|
||||
this.route({
|
||||
method: 'post',
|
||||
path: '/login',
|
||||
handler: this.login,
|
||||
permission: NONE,
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['auth'],
|
||||
operationId: 'login',
|
||||
requestBody: createRequestSchema('loginSchema'),
|
||||
responses: {
|
||||
200: createResponseSchema('userSchema'),
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
async login(req: IAuthRequest, res: Response): Promise<void> {
|
||||
async login(
|
||||
req: IAuthRequest<void, void, LoginSchema>,
|
||||
res: Response<UserSchema>,
|
||||
): Promise<void> {
|
||||
const { username, password } = req.body;
|
||||
|
||||
if (!username || !password) {
|
||||
res.status(400).json({
|
||||
message: 'You must provide username and password',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await this.userService.loginUser(username, password);
|
||||
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 { BackstageController } from './backstage';
|
||||
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 { IUnleashServices } from '../types/services';
|
||||
import { api } from './api-def';
|
||||
|
@ -2,9 +2,7 @@ import openapi, { IExpressOpenApi } from '@unleash/express-openapi';
|
||||
import { Express, RequestHandler, Response } from 'express';
|
||||
import { IUnleashConfig } from '../types/option';
|
||||
import {
|
||||
AdminApiOperation,
|
||||
ClientApiOperation,
|
||||
OtherApiOperation,
|
||||
ApiOperation,
|
||||
createOpenApiSchema,
|
||||
JsonSchemaProps,
|
||||
removeJsonSchemaProps,
|
||||
@ -31,9 +29,7 @@ export class OpenApiService {
|
||||
);
|
||||
}
|
||||
|
||||
validPath(
|
||||
op: AdminApiOperation | ClientApiOperation | OtherApiOperation,
|
||||
): RequestHandler {
|
||||
validPath(op: ApiOperation): RequestHandler {
|
||||
return this.api.validPath(op);
|
||||
}
|
||||
|
||||
|
@ -1041,6 +1041,22 @@ 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 {
|
||||
"additionalProperties": false,
|
||||
"properties": Object {
|
||||
@ -4950,7 +4966,7 @@ Object {
|
||||
},
|
||||
},
|
||||
"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 {
|
||||
"get": Object {
|
||||
"operationId": "getHealth",
|
||||
|
Loading…
Reference in New Issue
Block a user