Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop #6

Merged
merged 5 commits into from Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions api/e2e
@@ -0,0 +1,4 @@
#!/bin/bash

NODE_ENV=test node_modules/.bin/jest --config ./test/jest-e2e.json --detectOpenHandles $@

13 changes: 13 additions & 0 deletions api/src/db/cli/seeds/index.ts
Expand Up @@ -13,13 +13,26 @@ export const seed = async ({ appFactory, dataSource, logger }) => {
logger.info('connection is establised');

const users = [];
const userIds = [
'fa66f863-1040-48bd-a156-11bb7cce796e',
'4e5eb084-0499-47f8-a0d9-79603002e1bd',
'95bab443-1fda-4d63-90ca-2e13296650af',
'0060f247-3223-4512-9ce1-6bfaa18a2579',
'4195c4df-47fd-4b1b-b952-c7c38aa8f27f',
'f4d43b04-998d-4fcc-9afc-0dcbd0a5673a',
'1fe031c0-bf34-4684-ae29-baa9cf242398',
'03129556-4243-4c68-bdc5-b71c0cb129a3',
'c45beb14-3b18-4841-b1cc-ba6a84ac3067',
'9be550a1-7d6f-4488-af0d-a60989e90d3a',
];
await dataSource.transaction(async (manager: EntityManager) => {
logger.info('[START] users ========');
for (let i = 1; i <= 10; i++) {
const timestamp = new Date();
const user = manager.create(User, {
username: `user ${i}`,
email: `user.${i}@example.com`,
uuid: userIds[i - 1],
createdAt: timestamp,
updatedAt: timestamp,
status: 'active',
Expand Down
11 changes: 5 additions & 6 deletions api/src/domains/auth/auth.controller.ts
Expand Up @@ -28,13 +28,13 @@ export class AuthController {
@Res({ passthrough: true }) res: Response,
) {
const { email, password, rememberMe } = body;
const json = await this.authService.signIn(email, password);
const data = await this.authService.signIn(email, password);

if (rememberMe) {
this.setRefreshToken(res, json.refreshToken);
this.setRefreshToken(res, data.refreshToken);
}

return json;
return { data };
}

@Public()
Expand All @@ -54,12 +54,12 @@ export class AuthController {
});
if (!result) {
response.status(401);
return { message: 'invalid refresh token' };
return { message: 'invalid refresh token or uuid' };
}

this.setRefreshToken(response, result.refreshToken);

return result;
return { data: result };
} catch (e) {
response.status(401);
this.loggerService.logger.info(e);
Expand All @@ -72,7 +72,6 @@ export class AuthController {
@Delete('refresh')
async clearRefreshToken(@Res({ passthrough: true }) response: Response) {
this.removeRefreshToken(response);
response.status(200);
}

private extractTokenFromCookie(request: Request): string | undefined {
Expand Down
10 changes: 5 additions & 5 deletions api/src/domains/auth/auth.service.ts
Expand Up @@ -51,17 +51,17 @@ export class AuthService {
}

async token(user: User) {
await this.usersService.updateRefreshToken(user);
const _user = await this.usersService.updateRefreshToken(user);

const payload = {
sub: user.username,
refreshToken: user.refreshToken,
sub: _user.username,
refreshToken: _user.refreshToken,
};

return {
uuid: user.uuid,
uuid: _user.uuid,
accessToken: await this.jwtService.signAsync(payload),
refreshToken: user.refreshToken,
refreshToken: _user.refreshToken,
};
}

Expand Down
11 changes: 11 additions & 0 deletions api/src/domains/users/user.entity.ts
Expand Up @@ -36,4 +36,15 @@ export class User extends Base {

@OneToMany(() => Task, (task) => task.user)
tasks: Task[];

get serialize() {
return {
uuid: this.uuid,
username: this.username,
email: this.email,
status: this.status,
createdAt: this.createdAt,
updatedAt: this.updatedAt,
};
}
}
2 changes: 1 addition & 1 deletion api/src/domains/users/users.controller.ts
Expand Up @@ -6,6 +6,6 @@ export class UserController {
async show(@Req() request: Request): Promise<Record<string, any>> {
const user = request['user'];

return { data: user };
return { data: user.serialize };
}
}
2 changes: 2 additions & 0 deletions api/src/domains/users/users.service.ts
Expand Up @@ -68,6 +68,8 @@ export class UsersService {
async updateRefreshToken(user: User) {
user.refreshToken = await this.hashRefreshToken();
await this.manager.save(user);

return user;
}

async hash(user: User, password: string) {
Expand Down
105 changes: 105 additions & 0 deletions api/test/auth/auth.e2e-spec.ts
@@ -0,0 +1,105 @@
import { INestApplication } from '@nestjs/common';
import { prepareApp } from '../helper';
import request from '../helper/request';

describe('AuthController (e2e)', () => {
let app: INestApplication;
let server: any;

beforeAll(async () => {
app = await prepareApp([]);

server = app?.getHttpServer();
return;
});

describe('Post /auth/login', () => {
const subject = () => request(server).post('/auth/login');

it(`with right auth info, return 200`, async () => {
const response = await subject().send({
email: 'user.1@example.com',
password: 'AndyBobCharrie',
});

expect(response.status).toEqual(200);
expect(response.body).toMatchObject({
data: {
uuid: 'fa66f863-1040-48bd-a156-11bb7cce796e',
accessToken: expect.any(String),
refreshToken: expect.any(String),
},
});
});

it(`with wrong mail address, return 401: 1`, async () => {
const response = await subject().send({
email: 'wrong@example.com',
password: 'AndyBobCharrie',
});

expect(response.status).toEqual(401);
});

it(`with wrong password, return 401: 2`, async () => {
const response = await subject().send({
email: 'user.1@example.com',
password: 'wrongpassword',
});

expect(response.status).toEqual(401);
});
});

describe('Post /auth/refresh', () => {
const subject = () => request(server).post('/auth/refresh');

it(`with right refreshToken, return 200`, async () => {
const client = request(server);
const r1 = await client.post('/auth/login').send({
email: 'user.1@example.com',
password: 'AndyBobCharrie',
});

expect(r1.status).toEqual(200);
const { uuid, refreshToken } = r1.body.data;

const response = await client
.post('/auth/refresh')
.set('Cookie', `refreshToken=${refreshToken}`)
.send({
uuid,
});
expect(response.status).toEqual(200);
expect(response.body).toMatchObject({
data: {
uuid: 'fa66f863-1040-48bd-a156-11bb7cce796e',
accessToken: expect.any(String),
refreshToken: expect.any(String),
},
});
expect(response.body.data.refreshToken).not.toEqual(refreshToken);
});

it(`with wrong refreshToken, return 401: 1`, async () => {
const uuid = 'fa66f863-1040-48bd-a156-11bb7cce796e';
const response = await subject().withAuth().send({
uuid,
});

expect(response.status).toEqual(401);
});
});

describe('Delete /auth/refresh', () => {
const subject = () => request(server).delete('/auth/refresh');
it(`return 200`, async () => {
const response = await subject();
expect(response.status).toEqual(200);
});
});

afterAll(async () => {
await app.close();
});
});
6 changes: 3 additions & 3 deletions api/test/helper/databse.ts
@@ -1,6 +1,6 @@
import { INestApplication } from '@nestjs/common';
import { getDataSourceToken, getRepositoryToken } from '@nestjs/typeorm';
import { DataSource, EntityManager } from 'typeorm';
import { DataSource, EntityManager, Repository } from 'typeorm';

class RollbackError extends Error {}

Expand Down Expand Up @@ -28,6 +28,6 @@ export const withCleanup = async (
}
};

export const getRepository = (app: INestApplication<any>, token: any) => {
return app.get(getRepositoryToken(token));
export const getRepository = <T>(app: INestApplication<any>, token: any) => {
return app.get(getRepositoryToken(token)) as Repository<T>;
};
2 changes: 2 additions & 0 deletions api/test/helper/index.ts
Expand Up @@ -5,6 +5,7 @@ import { dataSourceOptions } from '../../src/db/config';
import appConfig from '../../src/config/app.config';
import { AppModule } from '../../src/app.module';
import { Test } from './request';
import cookieParser from 'cookie-parser';

export const checkNoAuthBehavior = (test: () => Test): [string, () => Test] => [
'no auth token, return 401',
Expand Down Expand Up @@ -39,6 +40,7 @@ export const prepareApp = async (providers: OverrideProvier[]) => {

const module = await moduleFixture.compile();
const app = module.createNestApplication();
app.use(cookieParser());
await app.init();

return app;
Expand Down
6 changes: 5 additions & 1 deletion api/test/helper/request.ts
Expand Up @@ -27,7 +27,7 @@ export type Test = ExtendedTest & supertest.Test;
const testFactory = (test: supertest.Test): Test =>
new Proxy(new ExtendedTest(test), handler) as Test;

class ExtendedRequest {
export class ExtendedRequest {
_request: supertest.SuperTest<supertest.Test>;

constructor(app: any) {
Expand All @@ -49,6 +49,10 @@ class ExtendedRequest {
put(path: string) {
return testFactory(this._request.put(path));
}

delete(path: string) {
return testFactory(this._request.delete(path));
}
}

export default function (app: any) {
Expand Down
5 changes: 1 addition & 4 deletions api/test/users/users.e2e-spec.ts
Expand Up @@ -31,14 +31,11 @@ describe('UsersController (e2e)', () => {
expect(response.status).toEqual(200);
expect(response.body).toMatchObject({
data: {
id: 1,
uuid: expect.any(String),
uuid: 'fa66f863-1040-48bd-a156-11bb7cce796e',
createdAt: expect.any(String),
updatedAt: expect.any(String),
username: 'user 1',
email: 'user.1@example.com',
password: expect.any(String),
refreshToken: expect.any(String),
status: 'active',
},
});
Expand Down
8 changes: 4 additions & 4 deletions frontend/core/src/app/layout.tsx
Expand Up @@ -3,19 +3,19 @@ import { Inter } from "next/font/google";

const inter = Inter({ subsets: ["latin"] });

import { Metadata } from 'next'
import { Metadata } from "next";

export const metadata: Metadata = {
title: 'Turbo',
}
title: "Turbo",
};

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<html lang="ja">
<body className={inter.className}>{children}</body>
</html>
);
Expand Down
8 changes: 5 additions & 3 deletions frontend/core/src/app/login/content.tsx
@@ -1,6 +1,6 @@
"use client";
import { useSearchParams } from "next/navigation";
import { useEffect } from "react";
import { useEffect, useState } from "react";
import styles from "./page.module.css";
import Header from "@/components/shared/header/public";
import Footer from "@/components/shared/footer";
Expand All @@ -12,15 +12,17 @@ import { useToast } from "@/lib/toast/hook";
const Content = () => {
const searchParams = useSearchParams();
const toast = useToast();
const [rendered, setRendered] = useState(false);

useEffect(() => {
const error = searchParams.get("error");
if (error) {
if (error === "loginRequired") {
if (!rendered && error === "loginRequired") {
toast.error("ログインが必要です");
}
}
}, [searchParams, toast]);
setRendered(true);
}, [searchParams, toast, rendered]);

return (
<>
Expand Down
26 changes: 26 additions & 0 deletions frontend/core/src/app/login/layout.tsx
@@ -0,0 +1,26 @@
import { Inter } from "next/font/google";

const inter = Inter({ subsets: ["latin"] });

import { Metadata } from "next";
import AuthContainer from "@/components/auth";

export const metadata: Metadata = {
title: "Turbo",
};

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<AuthContainer isPublic>
<html lang="ja">
<body className={inter.className}>{children}</body>
</html>
</AuthContainer>
);
}

export const dynamic = "error";
1 change: 0 additions & 1 deletion frontend/core/src/app/main/layout.module.css
@@ -1,5 +1,4 @@
.body {
height: calc(100vh - 72px);
}

.main {
Expand Down