mirror of
https://github.com/usatiuk/writer.git
synced 2025-10-29 00:17:48 +01:00
login form
This commit is contained in:
@@ -2,26 +2,96 @@ import "./Auth.scss";
|
|||||||
|
|
||||||
import { Button, Card, FormGroup, H2, InputGroup } from "@blueprintjs/core";
|
import { Button, Card, FormGroup, H2, InputGroup } from "@blueprintjs/core";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Dispatch } from "redux";
|
||||||
|
import { authStart } from "~redux/auth/actions";
|
||||||
|
import { IAppState } from "~redux/reducers";
|
||||||
|
|
||||||
export function Login() {
|
interface ILoginComponentProps {
|
||||||
return (
|
inProgress: boolean;
|
||||||
<>
|
error: string;
|
||||||
<Card className="AuthForm" elevation={2}>
|
login: (username: string, password: string) => void;
|
||||||
<form>
|
|
||||||
<H2>Login</H2>
|
|
||||||
<FormGroup label="Username">
|
|
||||||
<InputGroup leftIcon="person" />
|
|
||||||
</FormGroup>
|
|
||||||
<FormGroup label="Password">
|
|
||||||
<InputGroup leftIcon="key" />
|
|
||||||
</FormGroup>
|
|
||||||
<div className="buttons">
|
|
||||||
<Button className="submit" intent="primary">
|
|
||||||
Login
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Card>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ILoginComponentState {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LoginComponent extends React.PureComponent<
|
||||||
|
ILoginComponentProps,
|
||||||
|
ILoginComponentState
|
||||||
|
> {
|
||||||
|
constructor(props: ILoginComponentProps) {
|
||||||
|
super(props);
|
||||||
|
this.submit = this.submit.bind(this);
|
||||||
|
this.updateFields = this.updateFields.bind(this);
|
||||||
|
this.state = { username: "", password: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public submit() {
|
||||||
|
const { username, password } = this.state;
|
||||||
|
this.props.login(username, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateFields(e: React.FormEvent<HTMLInputElement>) {
|
||||||
|
const { value, name } = e.currentTarget;
|
||||||
|
this.setState({ ...this.state, [name]: value });
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Card className="AuthForm" elevation={2}>
|
||||||
|
<form>
|
||||||
|
<H2>Login</H2>
|
||||||
|
<FormGroup label="Username">
|
||||||
|
<InputGroup
|
||||||
|
name="username"
|
||||||
|
value={this.state.username}
|
||||||
|
onChange={this.updateFields}
|
||||||
|
leftIcon="person"
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup label="Password">
|
||||||
|
<InputGroup
|
||||||
|
name="password"
|
||||||
|
value={this.state.password}
|
||||||
|
onChange={this.updateFields}
|
||||||
|
type="password"
|
||||||
|
leftIcon="key"
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<div className="buttons">
|
||||||
|
<div id="errors">{this.props.error}</div>
|
||||||
|
<Button
|
||||||
|
className="submit"
|
||||||
|
intent="primary"
|
||||||
|
onClick={this.submit}
|
||||||
|
active={!this.props.inProgress}
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state: IAppState) {
|
||||||
|
return { inProgress: state.auth.inProgress, error: state.auth.error };
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch: Dispatch) {
|
||||||
|
return {
|
||||||
|
login: (username: string, password: string) =>
|
||||||
|
dispatch(authStart(username, password)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Login = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(LoginComponent);
|
||||||
|
|||||||
8
frontend/src/redux/api/auth/index.ts
Normal file
8
frontend/src/redux/api/auth/index.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { fetchJSON } from "../utils";
|
||||||
|
|
||||||
|
export async function login(username: string, password: string) {
|
||||||
|
return fetchJSON("/users/login", "POST", {
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
}
|
||||||
50
frontend/src/redux/api/utils.ts
Normal file
50
frontend/src/redux/api/utils.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
let token: string | null;
|
||||||
|
|
||||||
|
export function setToken(_token: string) {
|
||||||
|
token = _token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteToken(_token: string) {
|
||||||
|
token = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = "http://localhost:3000";
|
||||||
|
|
||||||
|
export async function fetchJSON(
|
||||||
|
path: string,
|
||||||
|
method: string,
|
||||||
|
body: string | object,
|
||||||
|
headers?: Record<string, string>,
|
||||||
|
) {
|
||||||
|
if (typeof body === "object") {
|
||||||
|
body = JSON.stringify(body);
|
||||||
|
}
|
||||||
|
const response = await fetch(root + path, {
|
||||||
|
method,
|
||||||
|
body,
|
||||||
|
headers: {
|
||||||
|
...headers,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const json = await response.json();
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchJSONAuth(
|
||||||
|
path: string,
|
||||||
|
method: string,
|
||||||
|
body: string | object,
|
||||||
|
headers?: object,
|
||||||
|
) {
|
||||||
|
if (token) {
|
||||||
|
return fetchJSON(path, method, body, {
|
||||||
|
...headers,
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,49 @@
|
|||||||
import { Action } from "redux";
|
import { Action } from "redux";
|
||||||
|
|
||||||
export const AUTH_SUCCESS = "AUTH_SUCCESS";
|
export enum AuthTypes {
|
||||||
|
AUTH_START = "AUTH_START",
|
||||||
class AuthSuccessAction implements Action {
|
AUTH_SUCCESS = "AUTH_SUCCESS",
|
||||||
public readonly type = AUTH_SUCCESS;
|
AUTH_FAIL = "AUTH_FAIL",
|
||||||
constructor(public jwt: string) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AuthAction = AuthSuccessAction;
|
export interface IAuthStartActionAction extends Action {
|
||||||
|
type: AuthTypes.AUTH_START;
|
||||||
|
payload: {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAuthSuccessActionAction extends Action {
|
||||||
|
type: AuthTypes.AUTH_SUCCESS;
|
||||||
|
payload: {
|
||||||
|
jwt: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAuthFailureActionAction extends Action {
|
||||||
|
type: AuthTypes.AUTH_FAIL;
|
||||||
|
payload: {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function authStart(
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
): IAuthStartActionAction {
|
||||||
|
return { type: AuthTypes.AUTH_START, payload: { username, password } };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function authSuccess(jwt: string): IAuthSuccessActionAction {
|
||||||
|
return { type: AuthTypes.AUTH_SUCCESS, payload: { jwt } };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function authFail(error: string): IAuthFailureActionAction {
|
||||||
|
return { type: AuthTypes.AUTH_FAIL, payload: { error } };
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AuthAction =
|
||||||
|
| IAuthStartActionAction
|
||||||
|
| IAuthSuccessActionAction
|
||||||
|
| IAuthFailureActionAction;
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
import { Reducer } from "react";
|
import { Reducer } from "react";
|
||||||
|
|
||||||
import { AUTH_SUCCESS, AuthAction } from "./actions";
|
import { AuthAction, AuthTypes } from "./actions";
|
||||||
|
|
||||||
export interface IAuthState {
|
export interface IAuthState {
|
||||||
jwt: string | null;
|
jwt: string | null;
|
||||||
inProgress: boolean;
|
inProgress: boolean;
|
||||||
|
error: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultAuthState: IAuthState = {
|
const defaultAuthState: IAuthState = {
|
||||||
jwt: null,
|
jwt: null,
|
||||||
inProgress: false,
|
inProgress: false,
|
||||||
|
error: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const auth: Reducer<IAuthState, AuthAction> = (
|
export const auth: Reducer<IAuthState, AuthAction> = (
|
||||||
@@ -17,8 +19,14 @@ export const auth: Reducer<IAuthState, AuthAction> = (
|
|||||||
action: AuthAction,
|
action: AuthAction,
|
||||||
) => {
|
) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case AUTH_SUCCESS:
|
case AuthTypes.AUTH_START:
|
||||||
return { ...state, jwt: action.jwt, inProgress: false };
|
return { ...state, inProgress: true };
|
||||||
|
break;
|
||||||
|
case AuthTypes.AUTH_SUCCESS:
|
||||||
|
return { ...state, jwt: action.payload.jwt, inProgress: false };
|
||||||
|
break;
|
||||||
|
case AuthTypes.AUTH_FAIL:
|
||||||
|
return { ...defaultAuthState, error: action.payload.error };
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
|
|||||||
38
frontend/src/redux/auth/sagas.ts
Normal file
38
frontend/src/redux/auth/sagas.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { delay } from "redux-saga";
|
||||||
|
import { call, put, race, takeLatest } from "redux-saga/effects";
|
||||||
|
import { login } from "~redux/api/auth";
|
||||||
|
import { setToken } from "~redux/api/utils";
|
||||||
|
|
||||||
|
import {
|
||||||
|
authFail,
|
||||||
|
authSuccess,
|
||||||
|
AuthTypes,
|
||||||
|
IAuthStartActionAction,
|
||||||
|
} from "./actions";
|
||||||
|
|
||||||
|
function* authStart(action: IAuthStartActionAction) {
|
||||||
|
const { username, password } = action.payload;
|
||||||
|
try {
|
||||||
|
const { response, timeout } = yield race({
|
||||||
|
response: call(login, username, password),
|
||||||
|
timeout: call(delay, 1000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (timeout) {
|
||||||
|
return put(authFail("Timeout"));
|
||||||
|
}
|
||||||
|
if (response.data) {
|
||||||
|
const user = response.data;
|
||||||
|
yield call(setToken, user.jwt);
|
||||||
|
yield put(authSuccess(user.jwt));
|
||||||
|
} else {
|
||||||
|
yield put(authFail(response.error));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
yield put(authFail(e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* authSaga() {
|
||||||
|
yield takeLatest(AuthTypes.AUTH_START, authStart);
|
||||||
|
}
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
import { combineReducers } from "redux";
|
import { combineReducers } from "redux";
|
||||||
import { auth } from "~redux/auth/reducer";
|
import { auth, IAuthState } from "~redux/auth/reducer";
|
||||||
|
|
||||||
|
export interface IAppState {
|
||||||
|
auth: IAuthState;
|
||||||
|
}
|
||||||
|
|
||||||
export const rootReducer = combineReducers({ auth });
|
export const rootReducer = combineReducers({ auth });
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import { createStore } from "redux";
|
import { applyMiddleware, createStore } from "redux";
|
||||||
|
import createSagaMiddlware from "redux-saga";
|
||||||
import { rootReducer } from "~redux/reducers";
|
import { rootReducer } from "~redux/reducers";
|
||||||
|
|
||||||
export const store = createStore(rootReducer);
|
import { authSaga } from "./auth/sagas";
|
||||||
|
|
||||||
|
const sagaMiddleware = createSagaMiddlware();
|
||||||
|
|
||||||
|
export const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));
|
||||||
|
|
||||||
|
sagaMiddleware.run(authSaga);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"dom"
|
"dom"
|
||||||
],
|
],
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"target": "es6",
|
"target": "es5",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
|
|||||||
71
package-lock.json
generated
71
package-lock.json
generated
@@ -68,6 +68,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@koa/cors": {
|
||||||
|
"version": "2.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@koa/cors/-/cors-2.2.3.tgz",
|
||||||
|
"integrity": "sha512-tCVVXa39ETsit5kGBtEWWimjLn1sDaeu8+0phgb8kT3GmBDZOykkI3ZO8nMjV2p3MGkJI4K5P+bxR8Ztq0bwsA==",
|
||||||
|
"requires": {
|
||||||
|
"vary": "^1.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/accepts": {
|
"@types/accepts": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
"resolved": "http://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz",
|
"resolved": "http://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz",
|
||||||
@@ -151,8 +159,7 @@
|
|||||||
"@types/events": {
|
"@types/events": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "http://registry.npmjs.org/@types/events/-/events-1.2.0.tgz",
|
"resolved": "http://registry.npmjs.org/@types/events/-/events-1.2.0.tgz",
|
||||||
"integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==",
|
"integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"@types/express": {
|
"@types/express": {
|
||||||
"version": "4.16.0",
|
"version": "4.16.0",
|
||||||
@@ -176,6 +183,15 @@
|
|||||||
"@types/range-parser": "*"
|
"@types/range-parser": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/formidable": {
|
||||||
|
"version": "1.0.31",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-1.0.31.tgz",
|
||||||
|
"integrity": "sha512-dIhM5t8lRP0oWe2HF8MuPvdd1TpPTjhDMAqemcq6oIZQCBQTovhBAdTQ5L5veJB4pdQChadmHuxtB0YzqvfU3Q==",
|
||||||
|
"requires": {
|
||||||
|
"@types/events": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/http-assert": {
|
"@types/http-assert": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.4.0.tgz",
|
||||||
@@ -223,15 +239,6 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/koa-bodyparser": {
|
|
||||||
"version": "4.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/koa-bodyparser/-/koa-bodyparser-4.2.1.tgz",
|
|
||||||
"integrity": "sha512-dd6mVT30OmGYIOmNRF3269Bv+IJ68AVrvYcPViB7bYnzxk7nZyfeAsUx96lvXmaTpOGF4XZ7WDCuSOd7Npi6pw==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"@types/koa": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@types/koa-compose": {
|
"@types/koa-compose": {
|
||||||
"version": "3.2.2",
|
"version": "3.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.2.tgz",
|
||||||
@@ -256,6 +263,15 @@
|
|||||||
"@types/koa": "*"
|
"@types/koa": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/koa__cors": {
|
||||||
|
"version": "2.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/koa__cors/-/koa__cors-2.2.3.tgz",
|
||||||
|
"integrity": "sha512-RfG2EuSc+nv/E+xbDSLW8KCoeri/3AkqwVPuENfF/DctllRoXhooboO//Sw7yFtkLvj7nG7O1H3JcZmoTQz8nQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/koa": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/lodash": {
|
"@types/lodash": {
|
||||||
"version": "4.14.119",
|
"version": "4.14.119",
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.119.tgz",
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.119.tgz",
|
||||||
@@ -1230,14 +1246,14 @@
|
|||||||
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
|
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
|
||||||
},
|
},
|
||||||
"co-body": {
|
"co-body": {
|
||||||
"version": "6.0.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/co-body/-/co-body-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/co-body/-/co-body-5.2.0.tgz",
|
||||||
"integrity": "sha512-9ZIcixguuuKIptnY8yemEOuhb71L/lLf+Rl5JfJEUiDNJk0e02MBt7BPxR2GEh5mw8dPthQYR4jPI/BnS1MQgw==",
|
"integrity": "sha512-sX/LQ7LqUhgyaxzbe7IqwPeTr2yfpfUIQ/dgpKo6ZI4y4lpQA0YxAomWIY+7I7rHWcG02PG+OuPREzMW/5tszQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"inflation": "^2.0.0",
|
"inflation": "^2.0.0",
|
||||||
"qs": "^6.5.2",
|
"qs": "^6.4.0",
|
||||||
"raw-body": "^2.3.3",
|
"raw-body": "^2.2.0",
|
||||||
"type-is": "^1.6.16"
|
"type-is": "^1.6.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"code-point-at": {
|
"code-point-at": {
|
||||||
@@ -1544,11 +1560,6 @@
|
|||||||
"keygrip": "~1.0.3"
|
"keygrip": "~1.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"copy-to": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/copy-to/-/copy-to-2.0.1.tgz",
|
|
||||||
"integrity": "sha1-JoD7uAaKSNCGVrYJgJK9r8kG9KU="
|
|
||||||
},
|
|
||||||
"core-util-is": {
|
"core-util-is": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||||
@@ -2313,8 +2324,7 @@
|
|||||||
"formidable": {
|
"formidable": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz",
|
||||||
"integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==",
|
"integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"fresh": {
|
"fresh": {
|
||||||
"version": "0.5.2",
|
"version": "0.5.2",
|
||||||
@@ -2811,13 +2821,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"koa-bodyparser": {
|
"koa-body": {
|
||||||
"version": "4.2.1",
|
"version": "4.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/koa-bodyparser/-/koa-bodyparser-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/koa-body/-/koa-body-4.0.6.tgz",
|
||||||
"integrity": "sha512-UIjPAlMZfNYDDe+4zBaOAUKYqkwAGcIU6r2ARf1UOXPAlfennQys5IiShaVeNf7KkVBlf88f2LeLvBFvKylttw==",
|
"integrity": "sha512-k03au8eI5FL48fA1YqnpDT9lcRaarHDEGUOes/ppaEbG6vhbU1xvegxP9QHvJz+UgQq75Vw8z3busd484I/PAA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"co-body": "^6.0.0",
|
"@types/formidable": "^1.0.31",
|
||||||
"copy-to": "^2.0.1"
|
"co-body": "^5.1.1",
|
||||||
|
"formidable": "^1.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"koa-compose": {
|
"koa-compose": {
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
"@types/eslint-plugin-prettier": "^2.2.0",
|
"@types/eslint-plugin-prettier": "^2.2.0",
|
||||||
"@types/jsonwebtoken": "^8.3.0",
|
"@types/jsonwebtoken": "^8.3.0",
|
||||||
"@types/koa": "^2.0.48",
|
"@types/koa": "^2.0.48",
|
||||||
"@types/koa-bodyparser": "^4.2.1",
|
|
||||||
"@types/koa-logger": "^3.1.1",
|
"@types/koa-logger": "^3.1.1",
|
||||||
"@types/koa-router": "^7.0.35",
|
"@types/koa-router": "^7.0.35",
|
||||||
|
"@types/koa__cors": "^2.2.3",
|
||||||
"@types/lodash": "^4.14.119",
|
"@types/lodash": "^4.14.119",
|
||||||
"@types/mocha": "^5.2.5",
|
"@types/mocha": "^5.2.5",
|
||||||
"@types/mysql": "^2.15.5",
|
"@types/mysql": "^2.15.5",
|
||||||
@@ -40,10 +40,11 @@
|
|||||||
"typescript": "3.2.2"
|
"typescript": "3.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@koa/cors": "^2.2.3",
|
||||||
"bcrypt": "^3.0.3",
|
"bcrypt": "^3.0.3",
|
||||||
"jsonwebtoken": "^8.4.0",
|
"jsonwebtoken": "^8.4.0",
|
||||||
"koa": "^2.6.2",
|
"koa": "^2.6.2",
|
||||||
"koa-bodyparser": "^4.2.1",
|
"koa-body": "^4.0.6",
|
||||||
"koa-jwt": "^3.5.1",
|
"koa-jwt": "^3.5.1",
|
||||||
"koa-logger": "^3.2.0",
|
"koa-logger": "^3.2.0",
|
||||||
"koa-router": "^7.4.0",
|
"koa-router": "^7.4.0",
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import "reflect-metadata";
|
import "reflect-metadata";
|
||||||
|
|
||||||
|
import * as cors from "@koa/cors";
|
||||||
import * as Koa from "koa";
|
import * as Koa from "koa";
|
||||||
import * as bodyParser from "koa-bodyparser";
|
import * as bodyParser from "koa-body";
|
||||||
import * as jwt from "koa-jwt";
|
import * as jwt from "koa-jwt";
|
||||||
import * as logger from "koa-logger";
|
import * as logger from "koa-logger";
|
||||||
import { config } from "~config";
|
import { config } from "~config";
|
||||||
@@ -9,6 +10,7 @@ import { userRouter } from "~routes/users";
|
|||||||
|
|
||||||
export const app = new Koa();
|
export const app = new Koa();
|
||||||
|
|
||||||
|
app.use(cors());
|
||||||
app.use(logger());
|
app.use(logger());
|
||||||
app.use(bodyParser());
|
app.use(bodyParser());
|
||||||
app.use(
|
app.use(
|
||||||
|
|||||||
@@ -15,14 +15,17 @@ userRouter.get("/users/user", async ctx => {
|
|||||||
ctx.body = { errors: false, data: user.toAuthJSON() };
|
ctx.body = { errors: false, data: user.toAuthJSON() };
|
||||||
});
|
});
|
||||||
|
|
||||||
userRouter.get("/users/login", async ctx => {
|
userRouter.post("/users/login", async ctx => {
|
||||||
if (!ctx.request.body) {
|
const request = ctx.request as any;
|
||||||
|
|
||||||
|
if (!request.body) {
|
||||||
ctx.throw(400);
|
ctx.throw(400);
|
||||||
}
|
}
|
||||||
const { username, password } = ctx.request.body as {
|
const { username, password } = request.body as {
|
||||||
username: string | null;
|
username: string | null;
|
||||||
password: string | null;
|
password: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!(username && password)) {
|
if (!(username && password)) {
|
||||||
ctx.throw(400);
|
ctx.throw(400);
|
||||||
}
|
}
|
||||||
@@ -35,12 +38,14 @@ userRouter.get("/users/login", async ctx => {
|
|||||||
ctx.body = { errors: false, data: user.toAuthJSON() };
|
ctx.body = { errors: false, data: user.toAuthJSON() };
|
||||||
});
|
});
|
||||||
|
|
||||||
userRouter.get("/users/signup", async ctx => {
|
userRouter.post("/users/signup", async ctx => {
|
||||||
if (!ctx.request.body) {
|
const request = ctx.request as any;
|
||||||
|
|
||||||
|
if (!request.body) {
|
||||||
ctx.throw(400);
|
ctx.throw(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { username, password } = ctx.request.body as {
|
const { username, password } = request.body as {
|
||||||
username: string | null;
|
username: string | null;
|
||||||
password: string | null;
|
password: string | null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,7 +27,10 @@ describe("users", () => {
|
|||||||
it("should get user", async () => {
|
it("should get user", async () => {
|
||||||
const response = await request(callback)
|
const response = await request(callback)
|
||||||
.get("/users/user")
|
.get("/users/user")
|
||||||
.set({ Authorization: `Bearer ${seed.user1.toJWT()}` })
|
.set({
|
||||||
|
Authorization: `Bearer ${seed.user1.toJWT()}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
})
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
@@ -40,7 +43,8 @@ describe("users", () => {
|
|||||||
|
|
||||||
it("should login user", async () => {
|
it("should login user", async () => {
|
||||||
const response = await request(callback)
|
const response = await request(callback)
|
||||||
.get("/users/login")
|
.post("/users/login")
|
||||||
|
.set({ "Content-Type": "application/json" })
|
||||||
.send({ username: "User1", password: "User1" })
|
.send({ username: "User1", password: "User1" })
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
@@ -54,7 +58,8 @@ describe("users", () => {
|
|||||||
|
|
||||||
it("should not login user with wrong password", async () => {
|
it("should not login user with wrong password", async () => {
|
||||||
const response = await request(callback)
|
const response = await request(callback)
|
||||||
.get("/users/login")
|
.post("/users/login")
|
||||||
|
.set({ "Content-Type": "application/json" })
|
||||||
.send({ username: "User1", password: "asdf" })
|
.send({ username: "User1", password: "asdf" })
|
||||||
.expect(404);
|
.expect(404);
|
||||||
|
|
||||||
@@ -63,7 +68,8 @@ describe("users", () => {
|
|||||||
|
|
||||||
it("should signup user", async () => {
|
it("should signup user", async () => {
|
||||||
const response = await request(callback)
|
const response = await request(callback)
|
||||||
.get("/users/signup")
|
.post("/users/signup")
|
||||||
|
.set({ "Content-Type": "application/json" })
|
||||||
.send({ username: "NUser1", password: "NUser1" })
|
.send({ username: "NUser1", password: "NUser1" })
|
||||||
.expect("Content-Type", /json/)
|
.expect("Content-Type", /json/)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
@@ -79,7 +85,8 @@ describe("users", () => {
|
|||||||
|
|
||||||
it("should not signup user with duplicate username", async () => {
|
it("should not signup user with duplicate username", async () => {
|
||||||
const response = await request(callback)
|
const response = await request(callback)
|
||||||
.get("/users/signup")
|
.post("/users/signup")
|
||||||
|
.set({ "Content-Type": "application/json" })
|
||||||
.send({ username: "User1", password: "NUser1" })
|
.send({ username: "User1", password: "NUser1" })
|
||||||
.expect(400);
|
.expect(400);
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"object-literal-sort-keys": false,
|
"object-literal-sort-keys": false,
|
||||||
"no-implicit-dependencies": false,
|
"no-implicit-dependencies": false,
|
||||||
"no-submodule-imports": false,
|
"no-submodule-imports": false,
|
||||||
"no-this-assignment": false
|
"no-this-assignment": false,
|
||||||
|
"max-classes-per-file": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user