Skip to content

Commit

Permalink
Implement auth api
Browse files Browse the repository at this point in the history
 (#11)
  • Loading branch information
shunsei committed Oct 14, 2024
1 parent ab8d99f commit 9b00dad
Show file tree
Hide file tree
Showing 7 changed files with 267 additions and 17 deletions.
13 changes: 8 additions & 5 deletions api/paths/auth.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ auth:
$ref: "../components/examples/user.yml"
'400':
$ref: "../components/responses/4xx.yml#/BadRequest"
'404':
$ref: "../components/responses/4xx.yml#/NotFound"
'500':
$ref: "../components/responses/5xx.yml#/InternalServerError"

Expand All @@ -56,7 +58,7 @@ auth:
description: CookieからセッションIDを削除する
security: []
responses:
'204':
'200':
description: ログアウトに成功した
headers:
Set-Cookie:
Expand All @@ -66,10 +68,11 @@ auth:
content:
application/json:
schema:
$ref: "../components/schemas/Error.yml"
type: object
properties:
message:
type: string
example:
message: "No Content"
'401':
$ref: "../components/responses/4xx.yml#/Unauthorized"
message: "OK"
'500':
$ref: "../components/responses/5xx.yml#/InternalServerError"
10 changes: 8 additions & 2 deletions api/paths/user.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ users:
content:
application/json:
schema:
$ref: "../components/schemas/Error.yml"
type: object
properties:
message:
type: string
example:
message: "Created"
'400':
Expand Down Expand Up @@ -206,7 +209,10 @@ user:
content:
application/json:
schema:
$ref: "../components/schemas/Error.yml"
type: object
properties:
message:
type: string
example:
message: "No Content"
'400':
Expand Down
3 changes: 2 additions & 1 deletion backend/drizzle/seeds/user.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ VALUES
('鈴木次郎', 'jiro.suzuki@example.com', '5d41402abc4b2a76b9719d911017c592'),
('高橋花子', 'hanako.takahashi@example.com', '5f4dcc3b5aa765d61d8327deb882cf99'),
('田中一郎', 'ichiro.tanaka@example.com', '6cb75f652a9b52798ebc5f8b9b9b57e9'),
('山田美咲', 'misaki.yamada@example.com', '2c6ee24b19816a6f14f95d1698b24ead');
('山田美咲', 'misaki.yamada@example.com', '2c6ee24b19816a6f14f95d1698b24ead'),
('比企谷八幡', 'hikigaya@oregairu.com', '5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8');
16 changes: 13 additions & 3 deletions backend/src/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,18 @@ app.post(
.from(userTable)
.where(eq(userTable.email, credential.email));

// ユーザが存在しない場合
if (user.length === 0) {
return ctx.notFound();
}

// ログイン済みか確認する
const loggedIn = await isLoggedIn(ctx);
if (!loggedIn) {
// パスワードが正しいか確認する
const hash = await generateHash(credential.password);
if (hash === user[0].passwordDigest) {
login(ctx, user[0].id);
await login(ctx, user[0].id);
} else {
return ctx.json(
{
Expand All @@ -63,8 +68,13 @@ app.post(
);

app.delete('/', async (ctx) => {
logout(ctx);
return ctx.body(null, 204);
await logout(ctx);
return ctx.json(
{
message: 'Goodbye',
},
200
);
});

export default app;
14 changes: 12 additions & 2 deletions backend/src/utils/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ export const login = async (ctx: Context, userId: number) => {
setCookie(
ctx,
'user_id',
userId.toString(), {
userId.toString(),
{
// HTTPS通信時のみCookieが送信される
secure: true,
// Secure属性が付与されていることを強要する
Expand All @@ -61,11 +62,20 @@ export const login = async (ctx: Context, userId: number) => {
.where(eq(userTable.id, userId));
// Cookieにセッショントークンを保存する
// prettier-ignore
setCookie(ctx, 'session_token', sessionToken, { secure: true, prefix: 'secure' });
setCookie(
ctx,
'session_token',
sessionToken,
{
secure: true,
prefix: 'secure'
}
);
};

export const logout = async (ctx: Context) => {
const userIdCookie = getCookie(ctx, 'user_id', 'secure');
console.log(userIdCookie);
// Cookieが存在しない場合
if (userIdCookie === undefined) {
return;
Expand Down
219 changes: 219 additions & 0 deletions backend/test/api/auth.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import { userTable } from '@/drizzle/schema';
import app from '@/src/index';
import { generateHash } from '@/src/utils/crypto';
import { env } from 'cloudflare:test';
import { eq } from 'drizzle-orm';
import { drizzle } from 'drizzle-orm/d1';

describe('POST /auth', async () => {
const db = drizzle(env.DB);
const account = {
name: '比企谷八幡',
email: 'hikigaya@oregairu.com',
password: 'password',
};
const digest = await generateHash(account.password);

beforeAll(async () => {
// prettier-ignore
await db
.insert(userTable)
.values({
name: account.name,
email: account.email,
passwordDigest: digest,
});
});

afterAll(async () => {
await db.delete(userTable);
});

it('should login successfully', async () => {
const response = await app.request(
'/auth',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: account.email,
password: account.password,
}),
},
env
);

expect(response.status).toBe(200);

const cookies = response.headers.get('Set-Cookie');
expect(cookies).toContain('user_id=');
expect(cookies).toContain('session_token=');

const user = await db
.select()
.from(userTable)
.where(eq(userTable.email, account.email));

expect(user[0].sessionToken).not.toBeNull();
});

it('should login successfully even if already logged in', async () => {
const response = await app.request(
'/auth',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Set-Cookie': `user_id=1; session_token=${digest}`,
},
body: JSON.stringify({
email: account.email,
password: account.password,
}),
},
env
);

expect(response.status).toBe(200);

const cookies = response.headers.get('Set-Cookie');
expect(cookies).toContain('user_id=');
expect(cookies).toContain('session_token=');
});

it('should return 400 when email is missing', async () => {
const response = await app.request(
'/auth',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
password: account.password,
}),
},
env
);

expect(response.status).toBe(400);
});

it('should return 400 when password is missing', async () => {
const response = await app.request(
'/auth',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: account.email,
}),
},
env
);

expect(response.status).toBe(400);
});

it('should return 401 when password is wrong', async () => {
const response = await app.request(
'/auth',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: account.email,
password: 'wrong password',
}),
},
env
);

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

it('should return 404 when account does not exist', async () => {
const response = await app.request(
'/auth',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'hoge@example.com',
password: account.password,
}),
},
env
);

expect(response.status).toBe(404);
});
});

describe('DELETE /auth', async () => {
const db = drizzle(env.DB);
const account = {
name: '比企谷八幡',
email: 'hikigaya@oregairu.com',
password: 'password',
};
const digest = await generateHash(account.password);

beforeAll(async () => {
// prettier-ignore
await db
.insert(userTable)
.values({
name: account.name,
email: account.email,
passwordDigest: digest,
sessionToken: crypto.randomUUID(),
});
});

afterAll(async () => {
await db.delete(userTable);
});

it('should logout successfully', async () => {
const response = await app.request(
'/auth',
{
method: 'DELETE',
headers: {
Cookie: `__Secure-user_id=1; __Secure-session_token=${digest}`,
},
},
env
);

expect(response.status).toBe(200);

const user = await db
.select()
.from(userTable)
.where(eq(userTable.email, account.email));

expect(user[0].sessionToken).toBeNull();
});

it('should logout successfully even when not logged in', async () => {
const response = await app.request(
'/auth',
{
method: 'DELETE',
},
env
);

expect(response.status).toBe(200);
});
});
9 changes: 5 additions & 4 deletions backend/test/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ await db
.prepare(
`
CREATE TABLE users (
id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
name text NOT NULL,
email text NOT NULL,
password_digest text NOT NULL
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
name TEXT NOT NULL,
email TEXT NOT NULL,
password_digest TEXT NOT NULL,
session_token TEXT
);
`
)
Expand Down

0 comments on commit 9b00dad

Please sign in to comment.