From bed539ed550a42311c0f9fce14e6adf0ba919031 Mon Sep 17 00:00:00 2001
From: Peter Briggs <146312751+pwbriggs@users.noreply.github.com>
Date: Sat, 11 May 2024 09:33:25 -0700
Subject: [PATCH 1/6] feat: :passport_control: initial work on auth and login
Install & set up remix-auth, by adding Cred and Session tables to the database, installing & setting up out first strategy, remix-auth-form (with lots of backend code to check passwords, etc.). This does not include a way to create user accounts, nor proper playwright tests (some tests should fail currently).
This is not a breaking change because we don't use the database in the app yet for any purpose that would conflict with this change.
---
app/components/profile.tsx | 13 +++
app/root.tsx | 3 +
app/routes/login.tsx | 80 +++++++++++++++++++
app/routes/my-schedule.tsx | 11 ++-
app/scripts/accounts.server.ts | 3 +
app/scripts/auth.server.ts | 39 +++++++++
app/scripts/password.server.ts | 30 +++++++
app/scripts/session.server.ts | 58 ++++++++++++++
app/styles/global-wide.css | 3 +-
package-lock.json | 44 ++++++++++
package.json | 4 +
.../20240321005217_auth/migration.sql | 24 ++++++
prisma/schema.prisma | 20 +++++
tests/auth.spec.ts | 7 ++
14 files changed, 337 insertions(+), 2 deletions(-)
create mode 100644 app/components/profile.tsx
create mode 100644 app/routes/login.tsx
create mode 100644 app/scripts/accounts.server.ts
create mode 100644 app/scripts/auth.server.ts
create mode 100644 app/scripts/password.server.ts
create mode 100644 app/scripts/session.server.ts
create mode 100644 prisma/migrations/20240321005217_auth/migration.sql
create mode 100644 tests/auth.spec.ts
diff --git a/app/components/profile.tsx b/app/components/profile.tsx
new file mode 100644
index 0000000..bed4a7a
--- /dev/null
+++ b/app/components/profile.tsx
@@ -0,0 +1,13 @@
+import { Link } from "@remix-run/react";
+
+export function ProfileMenu() {
+ // For now we just always display a login link: this is terrible UX, but simple.
+ // TODO show here if the user is logged in or not.
+ return
+}
+
+function GoToLogin() {
+ return (
+ Log in
+ );
+}
diff --git a/app/root.tsx b/app/root.tsx
index bdbee41..51994bf 100644
--- a/app/root.tsx
+++ b/app/root.tsx
@@ -13,6 +13,8 @@ import {
} from "@remix-run/react";
import { SpeedInsights } from "@vercel/speed-insights/remix"
+import { ProfileMenu } from "~/components/profile";
+
import faviconSvg from "~/images/favicon.svg";
import logo from "~/images/icon.svg";
@@ -53,6 +55,7 @@ export default function App() {
Your Schedule
All Events
Settings
+
diff --git a/app/routes/login.tsx b/app/routes/login.tsx
new file mode 100644
index 0000000..41770b3
--- /dev/null
+++ b/app/routes/login.tsx
@@ -0,0 +1,80 @@
+import { Form, useActionData } from "@remix-run/react";
+import { authenticator } from "~/scripts/auth.server";
+import { json } from "@remix-run/node";
+
+import type { ActionFunctionArgs, LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
+
+export const meta: MetaFunction = () => {
+ return [
+ { title: "Log in | BVS" },
+ { name: "description", content: "Log in to your account for personalized content." },
+ ];
+};
+
+export async function loader({ request }: LoaderFunctionArgs) {
+ // If the user is already authenticated redirect to home
+ return await authenticator.isAuthenticated(request, {
+ successRedirect: getNextUrl(request),
+ });
+}
+
+function getNextUrl(request: Request) {
+ try {
+ const url = new URL(request.url).searchParams.get("next");
+ if (typeof url !== "string") {
+ return "/";
+ }
+ try {
+ return new URL(url).pathname; // No cross-origin redirects happening here!
+ } catch {
+ return url;
+ }
+ } catch (e) {
+ return "/";
+ }
+}
+
+export async function action({ request }: ActionFunctionArgs) {
+ try {
+ return await authenticator.authenticate("usernamePassword", request, {
+ successRedirect: getNextUrl(request),
+ throwOnError: true
+ });
+ } catch (e) {
+ switch ((e as Error).message) {
+ case "badUsername":
+ return json({ formError: { message: "User not found" } });
+ break;
+ default:
+ return json({
+ formError: {
+ message: `Unknown authentication error "${(e as Error).message}"`
+ }
+ });
+ }
+ }
+}
+
+export default function Login() {
+ const actionData = useActionData();
+ return (
+
+ );
+}
diff --git a/app/routes/my-schedule.tsx b/app/routes/my-schedule.tsx
index d4d6872..bf91c2f 100644
--- a/app/routes/my-schedule.tsx
+++ b/app/routes/my-schedule.tsx
@@ -1,4 +1,6 @@
-import type { MetaFunction } from "@remix-run/node";
+import { authenticator } from "~/scripts/auth.server";
+
+import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
export const meta: MetaFunction = () => {
return [
@@ -7,6 +9,13 @@ export const meta: MetaFunction = () => {
];
};
+export async function loader({ request }: LoaderFunctionArgs) {
+ // If the user is already authenticated redirect to home
+ return await authenticator.isAuthenticated(request, {
+ failureRedirect: "/login?next=https://google.com/my-schedule",
+ });
+}
+
export default function MySchedule() {
return (
My Schedule
diff --git a/app/scripts/accounts.server.ts b/app/scripts/accounts.server.ts
new file mode 100644
index 0000000..99bb409
--- /dev/null
+++ b/app/scripts/accounts.server.ts
@@ -0,0 +1,3 @@
+export async function userExists(username: string) {
+ return await prisma.user.findUnique({ where: { username } }) !== null;
+}
diff --git a/app/scripts/auth.server.ts b/app/scripts/auth.server.ts
new file mode 100644
index 0000000..04d56c8
--- /dev/null
+++ b/app/scripts/auth.server.ts
@@ -0,0 +1,39 @@
+import { Authenticator } from "remix-auth";
+import { FormStrategy } from "remix-auth-form";
+import { sessionStorage } from "~/scripts/session.server";
+import { usernamePasswordLogin } from "~/scripts/password.server";
+import { prisma } from "~/scripts/prisma.server";
+
+import type { Session } from "~/scripts/session.server";
+import type { CredType } from "@prisma/client";
+
+export const authenticator = new Authenticator(sessionStorage);
+
+// Tell the Authenticator to use the form strategy
+authenticator.use(
+ new FormStrategy(async ({ form }) => {
+ let username = form.get("username");
+ let password = form.get("password");
+ if (typeof username !== "string" || typeof password !== "string") {
+ throw new TypeError("badFormData");
+ }
+ const session = await usernamePasswordLogin(
+ username,
+ password,
+ await getCredsByType(username, "USERNAME_PASSWORD")
+ );
+ return session;
+ }),
+ "usernamePassword"
+);
+
+async function getCredsByType(username: string, type: CredType) {
+ return await prisma.cred.findMany({
+ where: {
+ user: {
+ username
+ },
+ type
+ }
+ });
+}
diff --git a/app/scripts/password.server.ts b/app/scripts/password.server.ts
new file mode 100644
index 0000000..344802e
--- /dev/null
+++ b/app/scripts/password.server.ts
@@ -0,0 +1,30 @@
+import bcrypt from "bcryptjs";
+import { userExists } from "./accounts.server";
+
+import type { Cred } from "@prisma/client";
+import type { Session } from "~/scripts/session.server";
+
+export async function usernamePasswordLogin(
+ username: string,
+ password: string,
+ passwordCreds: Cred[]
+) {
+ if (passwordCreds.length == 0) {
+ if (!await userExists(username)) {
+ throw new Error("badUsername");
+ }
+ throw new Error("noPasswords");
+ }
+ if (password.length == 0) {
+ throw new Error("missingPassword");
+ }
+
+ for (const cred of passwordCreds) {
+ if (await bcrypt.compare(password, (cred.cred as { hash: string }).hash)) {
+ let session: Session = { username };
+ return session;
+ }
+ }
+
+ throw new Error("badPassword");
+}
diff --git a/app/scripts/session.server.ts b/app/scripts/session.server.ts
new file mode 100644
index 0000000..de393fa
--- /dev/null
+++ b/app/scripts/session.server.ts
@@ -0,0 +1,58 @@
+import { createSessionStorage } from "@remix-run/node";
+
+export type Session = {
+ username: string
+}
+
+function databaseSessionStorage(cookie: any) {
+ return createSessionStorage({
+ cookie,
+ async createData(data) {
+ const id = `${Date.now()}_${Math.floor(Math.random() * 2 ** 31)}`;
+ await prisma.session.create({
+ data: {
+ id,
+ data
+ }
+ });
+ return id;
+ },
+ async readData(id) {
+ return await prisma.session.findUnique({
+ where: { id }
+ })
+ },
+ async updateData(id, data) {
+ await prisma.session.update({
+ where: { id },
+ data: {
+ data
+ }
+ });
+ },
+ async deleteData(id) {
+ await prisma.session.delete({
+ where: { id }
+ });
+ },
+ });
+}
+
+const sessionSecret = process.env.NODE_ENV === "production" ? process.env.SESSION_SECRET : "dev";
+if (!sessionSecret) {
+ throw new Error("SESSION_SECRET must be set in prod");
+}
+
+export const sessionStorage = databaseSessionStorage({
+ cookie: {
+ name: "BVS_session",
+ secure: process.env.NODE_ENV === "production",
+ secrets: [sessionSecret],
+ sameSite: "lax",
+ path: "/",
+ maxAge: 60 * 60 * 24 * 30,
+ httpOnly: true,
+ },
+});
+
+export const { getSession, commitSession, destroySession } = sessionStorage;
diff --git a/app/styles/global-wide.css b/app/styles/global-wide.css
index 5e5ba4c..d0043b1 100644
--- a/app/styles/global-wide.css
+++ b/app/styles/global-wide.css
@@ -24,10 +24,11 @@ header.bvs-header > nav a {
padding: 0.8rem var(--edge-padding);
text-decoration: none;
font-weight: bold;
+ border-bottom: 0.2rem solid transparent;
}
header.bvs-header > nav a.active {
- border-bottom: 0.2rem solid;
+ border-bottom-color: white;
}
footer.bvs-footer {
diff --git a/package-lock.json b/package-lock.json
index 592daa9..2375aa3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,7 @@
"@remix-run/react": "^2.9.1",
"@vercel/remix": "^2.9.1",
"@vercel/speed-insights": "^1.0.10",
+ "bcryptjs": "^2.4.3",
"compression": "^1.7.4",
"cross-env": "^7.0.3",
"express": "^4.19.2",
@@ -21,12 +22,15 @@
"morgan": "^1.10.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
+ "remix-auth": "^3.6.0",
+ "remix-auth-form": "^1.4.0",
"source-map-support": "^0.5.21"
},
"devDependencies": {
"@playwright/test": "^1.44.0",
"@remix-run/dev": "^2.9.1",
"@remix-run/eslint-config": "^2.9.1",
+ "@types/bcryptjs": "^2.4.6",
"@types/compression": "^1.7.2",
"@types/express": "^4.17.17",
"@types/morgan": "^1.9.4",
@@ -2340,6 +2344,12 @@
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
"dev": true
},
+ "node_modules/@types/bcryptjs": {
+ "version": "2.4.6",
+ "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz",
+ "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==",
+ "dev": true
+ },
"node_modules/@types/body-parser": {
"version": "1.19.5",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
@@ -3849,6 +3859,11 @@
"node": ">= 0.8"
}
},
+ "node_modules/bcryptjs": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
+ "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="
+ },
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -10028,6 +10043,27 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/remix-auth": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/remix-auth/-/remix-auth-3.6.0.tgz",
+ "integrity": "sha512-mxlzLYi+/GKQSaXIqIw15dxAT1wm+93REAeDIft2unrKDYnjaGhhpapyPhdbALln86wt9lNAk21znfRss3fG7Q==",
+ "dependencies": {
+ "uuid": "^8.3.2"
+ },
+ "peerDependencies": {
+ "@remix-run/react": "^1.0.0 || ^2.0.0",
+ "@remix-run/server-runtime": "^1.0.0 || ^2.0.0"
+ }
+ },
+ "node_modules/remix-auth-form": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/remix-auth-form/-/remix-auth-form-1.4.0.tgz",
+ "integrity": "sha512-PirsVtv2AbJ7Lg+OjE+rjlW9AnkNYmqfmNIqTg0Mh1wur22ls5hxf2icVXVCRRhpcpV+FyoDxh03LtIyRj646A==",
+ "peerDependencies": {
+ "@remix-run/server-runtime": "^1.0.0 || ^2.0.0",
+ "remix-auth": "^3.6.0"
+ }
+ },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -11532,6 +11568,14 @@
"node": ">= 0.4.0"
}
},
+ "node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
"node_modules/uvu": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz",
diff --git a/package.json b/package.json
index d555ee3..30200e7 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
"@remix-run/react": "^2.9.1",
"@vercel/remix": "^2.9.1",
"@vercel/speed-insights": "^1.0.10",
+ "bcryptjs": "^2.4.3",
"compression": "^1.7.4",
"cross-env": "^7.0.3",
"express": "^4.19.2",
@@ -39,12 +40,15 @@
"morgan": "^1.10.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
+ "remix-auth": "^3.6.0",
+ "remix-auth-form": "^1.4.0",
"source-map-support": "^0.5.21"
},
"devDependencies": {
"@playwright/test": "^1.44.0",
"@remix-run/dev": "^2.9.1",
"@remix-run/eslint-config": "^2.9.1",
+ "@types/bcryptjs": "^2.4.6",
"@types/compression": "^1.7.2",
"@types/express": "^4.17.17",
"@types/morgan": "^1.9.4",
diff --git a/prisma/migrations/20240321005217_auth/migration.sql b/prisma/migrations/20240321005217_auth/migration.sql
new file mode 100644
index 0000000..45806cd
--- /dev/null
+++ b/prisma/migrations/20240321005217_auth/migration.sql
@@ -0,0 +1,24 @@
+-- CreateEnum
+CREATE TYPE "CredType" AS ENUM ('USERNAME_PASSWORD');
+
+-- CreateTable
+CREATE TABLE "Cred" (
+ "id" SERIAL NOT NULL,
+ "userId" INTEGER NOT NULL,
+ "type" "CredType" NOT NULL,
+ "cred" JSONB NOT NULL,
+ "created" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+
+ CONSTRAINT "Cred_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "Session" (
+ "id" TEXT NOT NULL,
+ "data" JSONB NOT NULL,
+
+ CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
+);
+
+-- AddForeignKey
+ALTER TABLE "Cred" ADD CONSTRAINT "Cred_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 0a153a9..28af809 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -17,6 +17,7 @@ model User {
managingRoles Role[] @relation("roleManager")
permissionSets UserPermissionSet[]
positions Position[]
+ Cred Cred[]
}
enum UserPermissionSet {
@@ -31,6 +32,25 @@ enum UserPermissionSet {
CREATE_EVENT
}
+model Cred {
+ id Int @id @default(autoincrement())
+ user User @relation(fields: [userId], references: [id])
+ userId Int
+ type CredType
+ cred Json
+ created DateTime @default(now())
+}
+
+enum CredType {
+ USERNAME_PASSWORD
+}
+
+/// A session on the website
+model Session {
+ id String @id
+ data Json
+}
+
model Role {
id Int @id @default(autoincrement())
name String @unique /// For URLs etc.
diff --git a/tests/auth.spec.ts b/tests/auth.spec.ts
new file mode 100644
index 0000000..bffaf6b
--- /dev/null
+++ b/tests/auth.spec.ts
@@ -0,0 +1,7 @@
+import { test, expect } from '@playwright/test';
+
+test.describe("Auth system", () => {
+ test('has actual tests', async ({ page }) => {
+ await expect(false).toBeTruthy();
+ });
+});
From 24bf808befb55e9bb42092dc9b231a255ce8b612 Mon Sep 17 00:00:00 2001
From: Peter Briggs <146312751+pwbriggs@users.noreply.github.com>
Date: Sat, 11 May 2024 09:33:25 -0700
Subject: [PATCH 2/6] fix: :ambulance: add missing prisma imports
Add missing imports of prisma, which exists as a global in development only.
---
app/scripts/accounts.server.ts | 2 ++
app/scripts/session.server.ts | 1 +
2 files changed, 3 insertions(+)
diff --git a/app/scripts/accounts.server.ts b/app/scripts/accounts.server.ts
index 99bb409..1a86ca7 100644
--- a/app/scripts/accounts.server.ts
+++ b/app/scripts/accounts.server.ts
@@ -1,3 +1,5 @@
+import { prisma } from "~/scripts/prisma.server";
+
export async function userExists(username: string) {
return await prisma.user.findUnique({ where: { username } }) !== null;
}
diff --git a/app/scripts/session.server.ts b/app/scripts/session.server.ts
index de393fa..4d9f87f 100644
--- a/app/scripts/session.server.ts
+++ b/app/scripts/session.server.ts
@@ -1,3 +1,4 @@
+import { prisma } from "~/scripts/prisma.server";
import { createSessionStorage } from "@remix-run/node";
export type Session = {
From 3ea20ef7b6ed4d0775e2165d047e7e4a94baa011 Mon Sep 17 00:00:00 2001
From: Peter Briggs <146312751+pwbriggs@users.noreply.github.com>
Date: Sat, 11 May 2024 09:33:25 -0700
Subject: [PATCH 3/6] fix: :adhesive_bandage: fix redirect link in your
schedule login
Oops, forgot to remove a test of the "next page after login" redirect sanitizer.
---
app/routes/my-schedule.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/routes/my-schedule.tsx b/app/routes/my-schedule.tsx
index bf91c2f..6e79c80 100644
--- a/app/routes/my-schedule.tsx
+++ b/app/routes/my-schedule.tsx
@@ -12,7 +12,7 @@ export const meta: MetaFunction = () => {
export async function loader({ request }: LoaderFunctionArgs) {
// If the user is already authenticated redirect to home
return await authenticator.isAuthenticated(request, {
- failureRedirect: "/login?next=https://google.com/my-schedule",
+ failureRedirect: "/login?next=/my-schedule",
});
}
From fab0ce0240368dbeac0a707c510b08f94683a18a Mon Sep 17 00:00:00 2001
From: Peter Briggs <146312751+pwbriggs@users.noreply.github.com>
Date: Sat, 11 May 2024 09:33:25 -0700
Subject: [PATCH 4/6] refactor: :art: clarify username vs user id
Change the utility function userExists to be called usernameExists, because users can technically be identified with a username or user id.
Also make usernameExists use a new (and exported) util function, getUserByUsername, which just runs a prisma query.
---
app/scripts/accounts.server.ts | 8 ++++++--
app/scripts/password.server.ts | 4 ++--
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/app/scripts/accounts.server.ts b/app/scripts/accounts.server.ts
index 1a86ca7..67e4e3a 100644
--- a/app/scripts/accounts.server.ts
+++ b/app/scripts/accounts.server.ts
@@ -1,5 +1,9 @@
import { prisma } from "~/scripts/prisma.server";
-export async function userExists(username: string) {
- return await prisma.user.findUnique({ where: { username } }) !== null;
+export async function getUserByUsername(username: string) {
+ return await prisma.user.findUnique({ where: { username } });
+}
+
+export async function usernameExists(username: string) {
+ return await getUserByUsername(username) !== null;
}
diff --git a/app/scripts/password.server.ts b/app/scripts/password.server.ts
index 344802e..5f3470c 100644
--- a/app/scripts/password.server.ts
+++ b/app/scripts/password.server.ts
@@ -1,5 +1,5 @@
import bcrypt from "bcryptjs";
-import { userExists } from "./accounts.server";
+import { usernameExists } from "./accounts.server";
import type { Cred } from "@prisma/client";
import type { Session } from "~/scripts/session.server";
@@ -10,7 +10,7 @@ export async function usernamePasswordLogin(
passwordCreds: Cred[]
) {
if (passwordCreds.length == 0) {
- if (!await userExists(username)) {
+ if (!await usernameExists(username)) {
throw new Error("badUsername");
}
throw new Error("noPasswords");
From 854eeb244cc66ac5e3881c60cdcf18bf3efd08eb Mon Sep 17 00:00:00 2001
From: Peter Briggs <146312751+pwbriggs@users.noreply.github.com>
Date: Sat, 11 May 2024 09:33:26 -0700
Subject: [PATCH 5/6] refactor: :recycle: put entire user object inside session
Optimization & ease of use, plus we already need to query the User table to check if the user exists, so why not bundle the request for the happy path?
---
app/scripts/password.server.ts | 11 ++++++-----
app/scripts/session.server.ts | 5 +++--
2 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/app/scripts/password.server.ts b/app/scripts/password.server.ts
index 5f3470c..e27c122 100644
--- a/app/scripts/password.server.ts
+++ b/app/scripts/password.server.ts
@@ -1,5 +1,5 @@
import bcrypt from "bcryptjs";
-import { usernameExists } from "./accounts.server";
+import { getUserByUsername } from "~/scripts/accounts.server";
import type { Cred } from "@prisma/client";
import type { Session } from "~/scripts/session.server";
@@ -9,10 +9,11 @@ export async function usernamePasswordLogin(
password: string,
passwordCreds: Cred[]
) {
+ const user = await getUserByUsername(username);
+ if (user == null) {
+ throw new Error("badUsername");
+ }
if (passwordCreds.length == 0) {
- if (!await usernameExists(username)) {
- throw new Error("badUsername");
- }
throw new Error("noPasswords");
}
if (password.length == 0) {
@@ -21,7 +22,7 @@ export async function usernamePasswordLogin(
for (const cred of passwordCreds) {
if (await bcrypt.compare(password, (cred.cred as { hash: string }).hash)) {
- let session: Session = { username };
+ let session: Session = { user };
return session;
}
}
diff --git a/app/scripts/session.server.ts b/app/scripts/session.server.ts
index 4d9f87f..bbe3121 100644
--- a/app/scripts/session.server.ts
+++ b/app/scripts/session.server.ts
@@ -1,9 +1,10 @@
import { prisma } from "~/scripts/prisma.server";
import { createSessionStorage } from "@remix-run/node";
+import type { User } from "@prisma/client";
export type Session = {
- username: string
-}
+ user: User
+};
function databaseSessionStorage(cookie: any) {
return createSessionStorage({
From a3912fa21f07ce9d495bfac2683730ab7c15c29e Mon Sep 17 00:00:00 2001
From: Peter Briggs <146312751+pwbriggs@users.noreply.github.com>
Date: Sat, 11 May 2024 09:33:26 -0700
Subject: [PATCH 6/6] test: :white_check_mark: expect unauthenticated requests
to My Schedule to redirect
Add a test that expects unauthenticated requests to My Schedule (/my-schedule) to redirect to the login page.
---
tests/auth.spec.ts | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/tests/auth.spec.ts b/tests/auth.spec.ts
index bffaf6b..a796000 100644
--- a/tests/auth.spec.ts
+++ b/tests/auth.spec.ts
@@ -4,4 +4,9 @@ test.describe("Auth system", () => {
test('has actual tests', async ({ page }) => {
await expect(false).toBeTruthy();
});
+
+ test("redirects unauthenticated requests to My Schedule", async ({ page }) => {
+ await page.goto("/my-schedule");
+ await expect(page).toHaveURL("/login?next=/my-schedule");
+ });
});