@batijs/cli 0.0.243 → 0.0.244
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boilerplates/@batijs/drizzle/files/$package.json.js +8 -8
- package/dist/boilerplates/@batijs/drizzle/files/database/{db.ts → drizzleDb.ts} +1 -2
- package/dist/boilerplates/@batijs/drizzle/files/database/seed.ts +4 -3
- package/dist/boilerplates/@batijs/drizzle/files/drizzle.config.ts +1 -1
- package/dist/boilerplates/@batijs/drizzle/types/database/drizzleDb.d.ts +1 -0
- package/dist/boilerplates/@batijs/express/files/$package.json.js +1 -0
- package/dist/boilerplates/@batijs/express/files/express-entry.ts +20 -0
- package/dist/boilerplates/@batijs/fastify/files/$package.json.js +1 -0
- package/dist/boilerplates/@batijs/fastify/files/fastify-entry.ts +20 -0
- package/dist/boilerplates/@batijs/h3/files/$package.json.js +1 -0
- package/dist/boilerplates/@batijs/h3/files/h3-entry.ts +20 -0
- package/dist/boilerplates/@batijs/hattip/files/$package.json.js +1 -0
- package/dist/boilerplates/@batijs/hattip/files/hattip-entry.ts +20 -0
- package/dist/boilerplates/@batijs/hono/files/$package.json.js +1 -0
- package/dist/boilerplates/@batijs/hono/files/hono-entry.ts +20 -0
- package/dist/boilerplates/@batijs/lucia-auth/files/$.env.js +23 -0
- package/dist/boilerplates/@batijs/lucia-auth/files/$README.md.js +29 -0
- package/dist/boilerplates/@batijs/lucia-auth/files/$package.json.js +133 -0
- package/dist/boilerplates/@batijs/lucia-auth/files/database/auth-actions.ts +54 -0
- package/dist/boilerplates/@batijs/lucia-auth/files/database/schema/auth.ts +41 -0
- package/dist/boilerplates/@batijs/lucia-auth/files/database/sqliteDb.ts +30 -0
- package/dist/boilerplates/@batijs/lucia-auth/files/lib/lucia-auth.ts +96 -0
- package/dist/boilerplates/@batijs/lucia-auth/files/pages/login/+guard.ts +11 -0
- package/dist/boilerplates/@batijs/lucia-auth/files/pages/login/style.css +94 -0
- package/dist/boilerplates/@batijs/lucia-auth/files/server/lucia-auth-handlers.ts +344 -0
- package/dist/boilerplates/@batijs/lucia-auth/files/vike.d.ts +11 -0
- package/dist/boilerplates/@batijs/lucia-auth/types/database/auth-actions.d.ts +9 -0
- package/dist/boilerplates/@batijs/lucia-auth/types/database/schema/auth.d.ts +167 -0
- package/dist/boilerplates/@batijs/lucia-auth/types/database/sqliteDb.d.ts +2 -0
- package/dist/boilerplates/@batijs/lucia-auth/types/lib/lucia-auth.d.ts +43 -0
- package/dist/boilerplates/@batijs/lucia-auth/types/pages/login/+guard.d.ts +3 -0
- package/dist/boilerplates/@batijs/lucia-auth/types/server/lucia-auth-handlers.d.ts +46 -0
- package/dist/boilerplates/@batijs/react/files/layouts/LayoutDefault.tsx +1 -0
- package/dist/boilerplates/@batijs/react/files/pages/+config.ts +1 -1
- package/dist/boilerplates/@batijs/react-lucia-auth/files/pages/login/+Page.tsx +96 -0
- package/dist/boilerplates/@batijs/react-lucia-auth/types/pages/login/+Page.d.ts +2 -0
- package/dist/boilerplates/@batijs/shared-server/files/server/create-todo-handler.ts +3 -3
- package/dist/boilerplates/@batijs/shared-todo/files/pages/todo/+data.ts +3 -3
- package/dist/boilerplates/@batijs/solid/files/layouts/LayoutDefault.tsx +1 -0
- package/dist/boilerplates/@batijs/solid/files/pages/+config.ts +1 -1
- package/dist/boilerplates/@batijs/solid-lucia-auth/files/pages/login/+Page.tsx +96 -0
- package/dist/boilerplates/@batijs/solid-lucia-auth/types/pages/login/+Page.d.ts +2 -0
- package/dist/boilerplates/@batijs/telefunc/files/pages/todo/TodoList.telefunc.ts +3 -3
- package/dist/boilerplates/@batijs/trpc/files/trpc/server.ts +3 -3
- package/dist/boilerplates/@batijs/ts-rest/files/server/ts-rest-handler.ts +3 -3
- package/dist/boilerplates/@batijs/vercel/files/$package.json.js +2 -2
- package/dist/boilerplates/@batijs/vue/files/layouts/LayoutDefault.vue +2 -0
- package/dist/boilerplates/@batijs/vue/files/pages/+config.ts +1 -1
- package/dist/boilerplates/@batijs/vue-lucia-auth/files/pages/login/+Page.vue +87 -0
- package/dist/boilerplates/boilerplates.json +59 -0
- package/dist/index.js +1 -1
- package/package.json +5 -5
- package/dist/boilerplates/@batijs/drizzle/types/database/db.d.ts +0 -2
- /package/dist/boilerplates/@batijs/drizzle/files/database/{schema.ts → schema/todos.ts} +0 -0
- /package/dist/boilerplates/@batijs/drizzle/types/database/{schema.d.ts → schema/todos.d.ts} +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import { Lucia } from "lucia";
|
|
3
|
+
import { BetterSqlite3Adapter } from "@lucia-auth/adapter-sqlite";
|
|
4
|
+
import { GitHub } from "arctic";
|
|
5
|
+
import { sqliteDb } from "../database/sqliteDb";
|
|
6
|
+
import { DrizzleSQLiteAdapter } from "@lucia-auth/adapter-drizzle";
|
|
7
|
+
import { drizzleDb } from "@batijs/drizzle/database/drizzleDb";
|
|
8
|
+
import { sessionTable, userTable } from "../database/schema/auth";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Polyfill needed if you're using Node.js 18 or below
|
|
12
|
+
*
|
|
13
|
+
* @link {@see https://lucia-auth.com/getting-started/#polyfill}
|
|
14
|
+
*/
|
|
15
|
+
if (!globalThis.crypto) {
|
|
16
|
+
Object.defineProperty(globalThis, "crypto", {
|
|
17
|
+
value: await import("node:crypto").then((crypto) => crypto.webcrypto as Crypto),
|
|
18
|
+
writable: false,
|
|
19
|
+
configurable: true,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Database setup
|
|
25
|
+
*
|
|
26
|
+
* @link {@see https://lucia-auth.com/database/#database-setup}
|
|
27
|
+
**/
|
|
28
|
+
const adapter = BATI.has("drizzle")
|
|
29
|
+
? new DrizzleSQLiteAdapter(drizzleDb, sessionTable, userTable)
|
|
30
|
+
: new BetterSqlite3Adapter(sqliteDb, {
|
|
31
|
+
user: "users",
|
|
32
|
+
session: "sessions",
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Initialize Lucia
|
|
37
|
+
*
|
|
38
|
+
* @link {@see https://lucia-auth.com/getting-started/#initialize-lucia}
|
|
39
|
+
*/
|
|
40
|
+
export const lucia = new Lucia(adapter, {
|
|
41
|
+
/**
|
|
42
|
+
* Lucia Configuration
|
|
43
|
+
*
|
|
44
|
+
* @link {@see https://lucia-auth.com/basics/configuration}
|
|
45
|
+
*/
|
|
46
|
+
sessionCookie: {
|
|
47
|
+
attributes: {
|
|
48
|
+
secure: process.env.NODE_ENV === "production",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
getUserAttributes: (attributes) => {
|
|
52
|
+
return {
|
|
53
|
+
username: attributes.username,
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Initialize OAuth provider
|
|
60
|
+
*
|
|
61
|
+
* @link {@see https://lucia-auth.com/guides/oauth/basics#initialize-oauth-provider}
|
|
62
|
+
*/
|
|
63
|
+
export const github = new GitHub(process.env.GITHUB_CLIENT_ID as string, process.env.GITHUB_CLIENT_SECRET as string);
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Define user attributes
|
|
67
|
+
*
|
|
68
|
+
* @link {@see https://lucia-auth.com/basics/users#define-user-attributes}
|
|
69
|
+
*/
|
|
70
|
+
declare module "lucia" {
|
|
71
|
+
interface Register {
|
|
72
|
+
Lucia: typeof lucia;
|
|
73
|
+
DatabaseUserAttributes: Omit<DatabaseUser, "id">;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface DatabaseUser {
|
|
78
|
+
id: string;
|
|
79
|
+
username: string;
|
|
80
|
+
password?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface DatabaseOAuthAccount {
|
|
84
|
+
provider_id: string;
|
|
85
|
+
provider_user_id: string;
|
|
86
|
+
/*{ @if (it.BATI.has("drizzle")) }*/
|
|
87
|
+
userId: string;
|
|
88
|
+
/*{ #else }*/
|
|
89
|
+
user_id: string;
|
|
90
|
+
/*{ /if }*/
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface GitHubUser {
|
|
94
|
+
id: number;
|
|
95
|
+
login: string; // username
|
|
96
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// https://vike.dev/guard
|
|
2
|
+
import { redirect } from "vike/abort";
|
|
3
|
+
import type { GuardAsync } from "vike/types";
|
|
4
|
+
|
|
5
|
+
const guard: GuardAsync = async (pageContext): ReturnType<GuardAsync> => {
|
|
6
|
+
if (pageContext.user) {
|
|
7
|
+
throw redirect("/");
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export { guard };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
header {
|
|
2
|
+
font-size: 28px;
|
|
3
|
+
font-weight: 600;
|
|
4
|
+
text-align: center;
|
|
5
|
+
}
|
|
6
|
+
.form {
|
|
7
|
+
position: absolute;
|
|
8
|
+
max-width: 430px;
|
|
9
|
+
width: 100%;
|
|
10
|
+
}
|
|
11
|
+
.form .field {
|
|
12
|
+
position: relative;
|
|
13
|
+
height: 50px;
|
|
14
|
+
width: 100%;
|
|
15
|
+
margin-top: 20px;
|
|
16
|
+
border-radius: 6px;
|
|
17
|
+
}
|
|
18
|
+
.field input,
|
|
19
|
+
.button-field {
|
|
20
|
+
height: 100%;
|
|
21
|
+
width: 100%;
|
|
22
|
+
border: none;
|
|
23
|
+
font-size: 16px;
|
|
24
|
+
font-weight: 400;
|
|
25
|
+
border-radius: 6px;
|
|
26
|
+
}
|
|
27
|
+
.button-group {
|
|
28
|
+
display: flex;
|
|
29
|
+
}
|
|
30
|
+
.login-button {
|
|
31
|
+
background-color: #0171d3e8;
|
|
32
|
+
}
|
|
33
|
+
.login-button:hover {
|
|
34
|
+
background-color: #016dcb;
|
|
35
|
+
}
|
|
36
|
+
.signup-button {
|
|
37
|
+
background-color: #d30101f6;
|
|
38
|
+
}
|
|
39
|
+
.signup-button:hover {
|
|
40
|
+
background-color: #cb010b;
|
|
41
|
+
}
|
|
42
|
+
.field input {
|
|
43
|
+
outline: none;
|
|
44
|
+
padding: 0 15px;
|
|
45
|
+
border: 1px solid#CACACA;
|
|
46
|
+
}
|
|
47
|
+
.field input:focus {
|
|
48
|
+
border-bottom-width: 2px;
|
|
49
|
+
}
|
|
50
|
+
.field button {
|
|
51
|
+
color: #fff;
|
|
52
|
+
margin: 0 2px;
|
|
53
|
+
transition: all 0.3s ease;
|
|
54
|
+
cursor: pointer;
|
|
55
|
+
}
|
|
56
|
+
.field-error {
|
|
57
|
+
display: block;
|
|
58
|
+
width: 100%;
|
|
59
|
+
margin-top: 0.25rem;
|
|
60
|
+
font-size: 85%;
|
|
61
|
+
color: #dc3545;
|
|
62
|
+
}
|
|
63
|
+
.media-options a {
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
justify-content: center;
|
|
67
|
+
}
|
|
68
|
+
a.github {
|
|
69
|
+
color: #fff;
|
|
70
|
+
background-color: #000;
|
|
71
|
+
}
|
|
72
|
+
a.github .github-icon {
|
|
73
|
+
height: 28px;
|
|
74
|
+
width: 28px;
|
|
75
|
+
color: #000;
|
|
76
|
+
font-size: 20px;
|
|
77
|
+
border-radius: 50%;
|
|
78
|
+
display: flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
justify-content: center;
|
|
81
|
+
background-color: #fff;
|
|
82
|
+
}
|
|
83
|
+
.github-icon {
|
|
84
|
+
position: absolute;
|
|
85
|
+
top: 50%;
|
|
86
|
+
left: 15px;
|
|
87
|
+
transform: translateY(-50%);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@media screen and (max-width: 400px) {
|
|
91
|
+
.form {
|
|
92
|
+
padding: 20px 10px;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import type { Session, User } from "lucia";
|
|
2
|
+
import { generateId, Scrypt, verifyRequestOrigin } from "lucia";
|
|
3
|
+
import type { DatabaseOAuthAccount, DatabaseUser, GitHubUser } from "../lib/lucia-auth";
|
|
4
|
+
import { github, lucia } from "../lib/lucia-auth";
|
|
5
|
+
import { SqliteError } from "better-sqlite3";
|
|
6
|
+
import { sqliteDb } from "../database/sqliteDb";
|
|
7
|
+
import { generateState, OAuth2RequestError } from "arctic";
|
|
8
|
+
import { parse, serialize } from "cookie";
|
|
9
|
+
import { drizzleDb } from "@batijs/drizzle/database/drizzleDb";
|
|
10
|
+
import { oauthAccountTable, userTable } from "../database/schema/auth";
|
|
11
|
+
import { getExistingAccount, getExistingUser, validateInput } from "../database/auth-actions";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* CSRF protection middleware
|
|
15
|
+
*
|
|
16
|
+
* @link {@see https://lucia-auth.com/guides/validate-session-cookies/}
|
|
17
|
+
*/
|
|
18
|
+
export function luciaCsrfMiddleware<Context extends Record<string | number | symbol, unknown>>(
|
|
19
|
+
request: Request,
|
|
20
|
+
_context?: Context,
|
|
21
|
+
): Response | undefined {
|
|
22
|
+
if (request.method === "GET") {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (!BATI_TEST) {
|
|
26
|
+
const originHeader = request.headers.get("Origin") ?? null;
|
|
27
|
+
const hostHeader = request.headers.get("Host") ?? null;
|
|
28
|
+
|
|
29
|
+
if (!originHeader || !hostHeader || !verifyRequestOrigin(originHeader, [hostHeader])) {
|
|
30
|
+
return new Response("Forbidden Request", {
|
|
31
|
+
status: 403,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Validate session cookies middleware
|
|
39
|
+
*
|
|
40
|
+
* @link {@see https://lucia-auth.com/guides/validate-session-cookies/}
|
|
41
|
+
*/
|
|
42
|
+
export async function luciaAuthMiddleware<Context extends Record<string | number | symbol, unknown>>(
|
|
43
|
+
request: Request,
|
|
44
|
+
context: Context & { session?: Session | null; user?: User | null },
|
|
45
|
+
): Promise<void> {
|
|
46
|
+
const sessionId = lucia.readSessionCookie(request.headers.get("cookie") ?? "");
|
|
47
|
+
|
|
48
|
+
if (!sessionId) {
|
|
49
|
+
context.user = null;
|
|
50
|
+
context.session = null;
|
|
51
|
+
} else {
|
|
52
|
+
const { session, user } = await lucia.validateSession(sessionId);
|
|
53
|
+
|
|
54
|
+
if (session?.fresh) {
|
|
55
|
+
request.headers.append("Set-Cookie", lucia.createSessionCookie(session.id).serialize());
|
|
56
|
+
}
|
|
57
|
+
if (!session) {
|
|
58
|
+
request.headers.append("Set-Cookie", lucia.createBlankSessionCookie().serialize());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
context.session = session;
|
|
62
|
+
context.user = user;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Register user handler
|
|
68
|
+
*
|
|
69
|
+
* @link {@see https://lucia-auth.com/guides/email-and-password/basics#register-user}
|
|
70
|
+
*/
|
|
71
|
+
export async function luciaAuthSignupHandler<Context extends Record<string | number | symbol, unknown>>(
|
|
72
|
+
request: Request,
|
|
73
|
+
_context?: Context,
|
|
74
|
+
): Promise<Response> {
|
|
75
|
+
const body = (await request.json()) as { username: string; password: string };
|
|
76
|
+
const username = body.username ?? "";
|
|
77
|
+
const password = body.password ?? "";
|
|
78
|
+
|
|
79
|
+
const validated = validateInput(username, password);
|
|
80
|
+
|
|
81
|
+
if (!validated.success) {
|
|
82
|
+
return new Response(JSON.stringify({ error: validated.error }), {
|
|
83
|
+
status: 422,
|
|
84
|
+
headers: {
|
|
85
|
+
"content-type": "application/json",
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* A pure JS implementation of Scrypt.
|
|
92
|
+
* It's portable but slower than implementations based on native code.
|
|
93
|
+
*
|
|
94
|
+
* @link {@see https://lucia-auth.com/reference/main/Scrypt}
|
|
95
|
+
* @link {@see https://lucia-auth.com/guides/email-and-password/basics#hashing-passwords}
|
|
96
|
+
*/
|
|
97
|
+
const scrypt = new Scrypt();
|
|
98
|
+
const passwordHash = await scrypt.hash(password);
|
|
99
|
+
|
|
100
|
+
const userId = generateId(15);
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
if (BATI.has("drizzle")) {
|
|
104
|
+
drizzleDb.insert(userTable).values({ id: userId, username, password: passwordHash }).run();
|
|
105
|
+
} else {
|
|
106
|
+
sqliteDb
|
|
107
|
+
.prepare("INSERT INTO users (id, username, password) VALUES(?, ?, ?)")
|
|
108
|
+
.run(userId, username, passwordHash);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const session = await lucia.createSession(userId, {});
|
|
112
|
+
|
|
113
|
+
return new Response(JSON.stringify({ status: "success" }), {
|
|
114
|
+
status: 200,
|
|
115
|
+
headers: {
|
|
116
|
+
"content-type": "application/json",
|
|
117
|
+
"set-cookie": lucia.createSessionCookie(session.id).serialize(),
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (error instanceof SqliteError && error.code === "SQLITE_CONSTRAINT_UNIQUE") {
|
|
122
|
+
return new Response(JSON.stringify({ error: { username: "Username already in use" } }), {
|
|
123
|
+
status: 422,
|
|
124
|
+
headers: {
|
|
125
|
+
"content-type": "application/json",
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return new Response(JSON.stringify({ error: { invalid: "An unknown error has occurred" } }), {
|
|
131
|
+
status: 500,
|
|
132
|
+
headers: {
|
|
133
|
+
"content-type": "application/json",
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Sign in user handler
|
|
141
|
+
*
|
|
142
|
+
* @link {@see https://lucia-auth.com/guides/email-and-password/basics#sign-in-user}
|
|
143
|
+
*/
|
|
144
|
+
export async function luciaAuthLoginHandler<Context extends Record<string | number | symbol, unknown>>(
|
|
145
|
+
request: Request,
|
|
146
|
+
_context?: Context,
|
|
147
|
+
): Promise<Response> {
|
|
148
|
+
const body = (await request.json()) as { username: string; password: string };
|
|
149
|
+
const username = body.username ?? "";
|
|
150
|
+
const password = body.password ?? "";
|
|
151
|
+
|
|
152
|
+
const validated = validateInput(username, password);
|
|
153
|
+
|
|
154
|
+
if (!validated.success) {
|
|
155
|
+
return new Response(JSON.stringify({ error: validated.error }), {
|
|
156
|
+
status: 422,
|
|
157
|
+
headers: {
|
|
158
|
+
"content-type": "application/json",
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const existingUser = getExistingUser(username) as DatabaseUser | undefined;
|
|
164
|
+
if (!existingUser) {
|
|
165
|
+
return new Response(JSON.stringify({ error: { invalid: "Incorrect username or password" } }), {
|
|
166
|
+
status: 422,
|
|
167
|
+
headers: {
|
|
168
|
+
"content-type": "application/json",
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const scrypt = new Scrypt();
|
|
174
|
+
const validPassword = existingUser.password && (await scrypt.verify(existingUser.password, password));
|
|
175
|
+
|
|
176
|
+
if (!validPassword) {
|
|
177
|
+
return new Response(JSON.stringify({ error: { invalid: "Incorrect username or password" } }), {
|
|
178
|
+
status: 422,
|
|
179
|
+
headers: {
|
|
180
|
+
"content-type": "application/json",
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const session = await lucia.createSession(existingUser.id, {});
|
|
186
|
+
|
|
187
|
+
return new Response(JSON.stringify({ status: "success" }), {
|
|
188
|
+
status: 200,
|
|
189
|
+
headers: {
|
|
190
|
+
"content-type": "application/json",
|
|
191
|
+
"set-cookie": lucia.createSessionCookie(session.id).serialize(),
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Log out user handler
|
|
198
|
+
*/
|
|
199
|
+
export async function luciaAuthLogoutHandler<Context extends Record<string | number | symbol, unknown>>(
|
|
200
|
+
_request: Request,
|
|
201
|
+
context: Context & { session?: Session | null },
|
|
202
|
+
): Promise<Response> {
|
|
203
|
+
const session = context.session ?? null;
|
|
204
|
+
|
|
205
|
+
if (!session) {
|
|
206
|
+
return new Response("Unauthorized Request", {
|
|
207
|
+
status: 401,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Invalidate sessions
|
|
212
|
+
*
|
|
213
|
+
* @link {@see https://lucia-auth.com/basics/sessions#invalidate-sessions}
|
|
214
|
+
*/
|
|
215
|
+
await lucia.invalidateSession(session.id);
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Delete session cookie
|
|
219
|
+
*
|
|
220
|
+
* @link {@see https://lucia-auth.com/basics/sessions#delete-session-cookie}
|
|
221
|
+
*/
|
|
222
|
+
return new Response(JSON.stringify({ status: "success" }), {
|
|
223
|
+
status: 200,
|
|
224
|
+
headers: {
|
|
225
|
+
"set-cookie": lucia.createBlankSessionCookie().serialize(),
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Github OAuth authorization handler
|
|
232
|
+
*
|
|
233
|
+
* @link {@see https://lucia-auth.com/guides/oauth/basics#creating-authorization-url}
|
|
234
|
+
*/
|
|
235
|
+
export async function luciaGithubLoginHandler<Context extends Record<string | number | symbol, unknown>>(
|
|
236
|
+
_request: Request,
|
|
237
|
+
_context?: Context,
|
|
238
|
+
): Promise<Response> {
|
|
239
|
+
const state = generateState();
|
|
240
|
+
const url = await github.createAuthorizationURL(state);
|
|
241
|
+
|
|
242
|
+
return new Response(null, {
|
|
243
|
+
status: 302,
|
|
244
|
+
headers: {
|
|
245
|
+
Location: url.toString(),
|
|
246
|
+
"set-cookie": serialize("github_oauth_state", state, {
|
|
247
|
+
path: "/",
|
|
248
|
+
secure: process.env.NODE_ENV === "production",
|
|
249
|
+
httpOnly: true,
|
|
250
|
+
maxAge: 60 * 10,
|
|
251
|
+
sameSite: "lax",
|
|
252
|
+
}),
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Github OAuth validate callback handler
|
|
259
|
+
*
|
|
260
|
+
* @link {@see https://lucia-auth.com/guides/oauth/basics#validate-callback}
|
|
261
|
+
*/
|
|
262
|
+
export async function luciaGithubCallbackHandler<Context extends Record<string | number | symbol, unknown>>(
|
|
263
|
+
request: Request,
|
|
264
|
+
_context?: Context,
|
|
265
|
+
): Promise<Response> {
|
|
266
|
+
const cookies = parse(request.headers.get("cookie") ?? "");
|
|
267
|
+
const params = new URL(request.url).searchParams;
|
|
268
|
+
const code = params.get("code");
|
|
269
|
+
const state = params.get("state");
|
|
270
|
+
const storedState = cookies.github_oauth_state ?? null;
|
|
271
|
+
|
|
272
|
+
if (!code || !state || !storedState || state !== storedState) {
|
|
273
|
+
return new Response("Unauthorized Request", {
|
|
274
|
+
status: 401,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
const tokens = await github.validateAuthorizationCode(code);
|
|
280
|
+
const githubUserResponse = await fetch("https://api.github.com/user", {
|
|
281
|
+
headers: {
|
|
282
|
+
Authorization: `Bearer ${tokens.accessToken}`,
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
const githubUser = (await githubUserResponse.json()) as GitHubUser;
|
|
286
|
+
|
|
287
|
+
const existingAccount = getExistingAccount("github", githubUser.id) as DatabaseOAuthAccount | undefined;
|
|
288
|
+
|
|
289
|
+
if (existingAccount) {
|
|
290
|
+
const session = await lucia.createSession(
|
|
291
|
+
BATI.has("drizzle") ? existingAccount.userId : existingAccount.user_id,
|
|
292
|
+
{},
|
|
293
|
+
);
|
|
294
|
+
return new Response(null, {
|
|
295
|
+
status: 302,
|
|
296
|
+
headers: {
|
|
297
|
+
Location: "/",
|
|
298
|
+
"set-cookie": lucia.createSessionCookie(session.id).serialize(),
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const userId = generateId(15);
|
|
304
|
+
|
|
305
|
+
if (BATI.has("drizzle")) {
|
|
306
|
+
await drizzleDb.transaction(async (tx) => {
|
|
307
|
+
await tx.insert(userTable).values({ id: userId, username: githubUser.login });
|
|
308
|
+
await tx.insert(oauthAccountTable).values({ providerId: "github", providerUserId: githubUser.id, userId });
|
|
309
|
+
});
|
|
310
|
+
} else {
|
|
311
|
+
sqliteDb.transaction(() => {
|
|
312
|
+
sqliteDb.prepare("INSERT INTO users (id, username) VALUES (?, ?)").run(userId, githubUser.login);
|
|
313
|
+
sqliteDb
|
|
314
|
+
.prepare("INSERT INTO oauth_accounts (provider_id, provider_user_id, user_id) VALUES (?, ?, ?)")
|
|
315
|
+
.run("github", githubUser.id, userId);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const session = await lucia.createSession(userId, {});
|
|
320
|
+
|
|
321
|
+
return new Response(null, {
|
|
322
|
+
status: 302,
|
|
323
|
+
headers: {
|
|
324
|
+
Location: "/",
|
|
325
|
+
"set-cookie": lucia.createSessionCookie(session.id).serialize(),
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
} catch (error) {
|
|
329
|
+
if (error instanceof OAuth2RequestError && error.message === "bad_verification_code") {
|
|
330
|
+
return new Response(JSON.stringify({ error: error.message }), {
|
|
331
|
+
status: 400,
|
|
332
|
+
headers: {
|
|
333
|
+
"content-type": "application/json",
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
return new Response(JSON.stringify({ error: error }), {
|
|
338
|
+
status: 500,
|
|
339
|
+
headers: {
|
|
340
|
+
"content-type": "application/json",
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function getExistingUser(username: string): unknown;
|
|
2
|
+
export declare function getExistingAccount(providerId: string, providerUserId: number): unknown;
|
|
3
|
+
export declare function validateInput(username: string | null, password: string | null): {
|
|
4
|
+
error: {
|
|
5
|
+
username: string | null;
|
|
6
|
+
password: string | null;
|
|
7
|
+
};
|
|
8
|
+
success: boolean;
|
|
9
|
+
};
|