Skip to content

Commit cece9d9

Browse files
authored
Merge pull request #23 from samuliasmala/antti/login
Valmis, mergetään!
2 parents cf2d4dc + 57a8e30 commit cece9d9

33 files changed

+3123
-1248
lines changed

.env.example

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
DATABASE_URL="postgresql://databaseusername:databasepassword@localhost:5432/mydb?schema=public"
1+
DATABASE_URL="postgresql://databaseusername:databasepassword@localhost:5432/mydb?schema=public" # README.md's section "Setting the environment variables" will help with this

backend/auth.ts

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { PrismaAdapter } from '@lucia-auth/adapter-prisma';
2+
import type { IncomingMessage, ServerResponse } from 'http';
3+
import { Lucia, TimeSpan } from 'lucia';
4+
import prisma from '~/prisma';
5+
import type { PrismaUser, User } from '~/shared/types';
6+
import type { Session, User as LuciaUser } from 'lucia';
7+
8+
export const adapter = new PrismaAdapter(prisma.session, prisma.user);
9+
10+
export const lucia = new Lucia(adapter, {
11+
sessionExpiresIn: new TimeSpan(1, 'h'),
12+
sessionCookie: {
13+
attributes: {
14+
secure: process.env.NODE_ENV === 'production',
15+
},
16+
},
17+
getUserAttributes(user): User {
18+
const { uuid, firstName, lastName, email, createdAt, updatedAt } = user;
19+
return { uuid, firstName, lastName, email, createdAt, updatedAt };
20+
},
21+
});
22+
23+
export const luciaLongSession = new Lucia(adapter, {
24+
sessionExpiresIn: new TimeSpan(30, 'd'),
25+
sessionCookie: {
26+
attributes: {
27+
secure: process.env.NODE_ENV === 'production',
28+
},
29+
},
30+
getUserAttributes(user): User {
31+
const { uuid, firstName, lastName, email, createdAt, updatedAt } = user;
32+
return { uuid, firstName, lastName, email, createdAt, updatedAt };
33+
},
34+
});
35+
36+
declare module 'lucia' {
37+
interface Register {
38+
Lucia: typeof lucia;
39+
DatabaseUserAttributes: PrismaUser;
40+
DatabaseSessionAttributes: { userUUID: string };
41+
}
42+
}
43+
44+
export async function validateRequest(
45+
req: IncomingMessage,
46+
res: ServerResponse,
47+
): Promise<
48+
{ user: LuciaUser; session: Session } | { user: null; session: null }
49+
> {
50+
const sessionId = lucia.readSessionCookie(req.headers.cookie ?? '');
51+
if (!sessionId) {
52+
return {
53+
user: null,
54+
session: null,
55+
};
56+
}
57+
const result = await lucia.validateSession(sessionId);
58+
if (result.session && result.session.fresh) {
59+
res.appendHeader(
60+
'Set-Cookie',
61+
lucia.createSessionCookie(result.session.id).serialize(),
62+
);
63+
}
64+
if (!result.session) {
65+
res.appendHeader(
66+
'Set-Cookie',
67+
lucia.createBlankSessionCookie().serialize(),
68+
);
69+
}
70+
71+
return result;
72+
}

backend/utils.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { compare as bcryptCompare, hash } from 'bcrypt';
2+
3+
export async function verifyPassword(
4+
givenPassword: string,
5+
hashedPassword: string,
6+
): Promise<boolean> {
7+
const isMatch = await bcryptCompare(givenPassword, hashedPassword);
8+
return isMatch;
9+
}
10+
11+
export async function hashPassword(password: string): Promise<string> {
12+
const saltRounds = 10;
13+
const hashedPassword = await hash(password, saltRounds);
14+
return hashedPassword;
15+
}
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { SVGProps } from 'react';
2+
3+
const SvgArrowRightStartOnRectangle = (props: SVGProps<SVGSVGElement>) => (
4+
<svg
5+
xmlns="http://www.w3.org/2000/svg"
6+
fill="none"
7+
stroke="currentColor"
8+
strokeWidth={1.5}
9+
className="arrow-right-start-on-rectangle_svg__w-6 arrow-right-start-on-rectangle_svg__h-6"
10+
viewBox="0 0 24 24"
11+
{...props}
12+
>
13+
<path
14+
strokeLinecap="round"
15+
strokeLinejoin="round"
16+
d="M15.75 9V5.25A2.25 2.25 0 0 0 13.5 3h-6a2.25 2.25 0 0 0-2.25 2.25v13.5A2.25 2.25 0 0 0 7.5 21h6a2.25 2.25 0 0 0 2.25-2.25V15m3 0 3-3m0 0-3-3m3 3H9"
17+
/>
18+
</svg>
19+
);
20+
export default SvgArrowRightStartOnRectangle;

icons/eye_open.tsx

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import type { SVGProps } from 'react';
2+
3+
const SvgEyeOpen = (props: SVGProps<SVGSVGElement>) => (
4+
<svg
5+
xmlns="http://www.w3.org/2000/svg"
6+
fill="none"
7+
stroke="currentColor"
8+
strokeWidth={1.5}
9+
className="eye_open_svg__w-6 eye_open_svg__h-6"
10+
viewBox="0 0 24 24"
11+
{...props}
12+
>
13+
<path
14+
strokeLinecap="round"
15+
strokeLinejoin="round"
16+
d="M2.036 12.322a1 1 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z"
17+
/>
18+
<path
19+
strokeLinecap="round"
20+
strokeLinejoin="round"
21+
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0"
22+
/>
23+
</svg>
24+
);
25+
export default SvgEyeOpen;

icons/eye_slash.tsx

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { SVGProps } from 'react';
2+
3+
const SvgEyeSlash = (props: SVGProps<SVGSVGElement>) => (
4+
<svg
5+
xmlns="http://www.w3.org/2000/svg"
6+
fill="none"
7+
stroke="currentColor"
8+
strokeWidth={1.5}
9+
className="eye_slash_svg__w-6 eye_slash_svg__h-6"
10+
viewBox="0 0 24 24"
11+
{...props}
12+
>
13+
<path
14+
strokeLinecap="round"
15+
strokeLinejoin="round"
16+
d="M3.98 8.223A10.5 10.5 0 0 0 1.934 12c1.292 4.338 5.31 7.5 10.066 7.5.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.52 10.52 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88"
17+
/>
18+
</svg>
19+
);
20+
export default SvgEyeSlash;

icons/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export { default as ArrowRightStartOnRectangle } from './arrow_right_start_on_rectangle';
2+
export { default as EyeOpen } from './eye_open';
3+
export { default as EyeSlash } from './eye_slash';
4+
export { default as User } from './user';

icons/user.tsx

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { SVGProps } from 'react';
2+
3+
const SvgUser = (props: SVGProps<SVGSVGElement>) => (
4+
<svg
5+
xmlns="http://www.w3.org/2000/svg"
6+
fill="none"
7+
stroke="currentColor"
8+
strokeWidth={1.5}
9+
className="user_svg__w-6 user_svg__h-6"
10+
viewBox="0 0 24 24"
11+
{...props}
12+
>
13+
<path
14+
strokeLinecap="round"
15+
strokeLinejoin="round"
16+
d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0M4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.9 17.9 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632"
17+
/>
18+
</svg>
19+
);
20+
export default SvgUser;

middleware.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { verifyRequestOrigin } from 'lucia';
2+
import { NextResponse } from 'next/server';
3+
import type { NextRequest } from 'next/server';
4+
5+
//eslint-disable-next-line
6+
export async function middleware(req: NextRequest): Promise<NextResponse> {
7+
if (req.method === 'GET') {
8+
return NextResponse.next();
9+
}
10+
const originHeader = req.headers.get('Origin');
11+
const hostHeader = req.headers.get('Host');
12+
13+
if (
14+
!originHeader ||
15+
!hostHeader ||
16+
!verifyRequestOrigin(originHeader, [hostHeader])
17+
) {
18+
return new NextResponse(null, { status: 403 });
19+
}
20+
return NextResponse.next();
21+
}

0 commit comments

Comments
 (0)