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

feat(auth): implemented NextAuth with dummy provider #33

Merged
merged 13 commits into from
Oct 30, 2023
29 changes: 29 additions & 0 deletions app/front-end/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,35 @@ To serve images, fonts, or downloadable files we must place them in the `./publi

To have common scripts across pages and components, we must place them in the `./src/lib` folder. This is for custom scripts that are not React components or API routes. For example, string manipulation, date formatting, etc. This makes a good place for things like database abstraction code, such as a class and its subclasses that are used to query a database. These _are not_ accessible to users from the web.

### Authentication

Components on the front end should make use of the provided `useSession()` hook from `next-auth/client` to determine if a user is logged in. This hook returns a `session` object that contains the user's information if they are logged in, or `null` if they are not. There is an example of its use in `./src/app/page.tsx`. The `status` variable is an additional convenience that takes the place of something like a `loading` state variable.

For authentication to work in your development environment, you must include a `.env.local` in the project root with the following variables:

```dotenv
# this would be the canonical url on the web when deployed
NEXTAUTH_URL=http://localhost:3000
tthvo marked this conversation as resolved.
Show resolved Hide resolved
NEXTAUTH_SECRET=secret
```

Where `NEXTAUTH_SECRET` is a random string of characters.

If a new key needs to be generated, run the following command:

```bash
openssl rand -base64 32
```

During development and testing, the following credentials will be used to log in:

```json
{
"email": "johnny@example.com",
tthvo marked this conversation as resolved.
Show resolved Hide resolved
"password": "password"
}
```

## TESTING

Next.js 13 is compatible with Jest and React Testing Library out of the box. The appropriate packages are already installed. Next.js's documentation can be found here: https://nextjs.org/docs/pages/building-your-application/optimizing/testing#jest-and-react-testing-library.
Expand Down
36 changes: 33 additions & 3 deletions app/front-end/__tests__/__snapshots__/index.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@
exports[`Home homepage is unchanged from snapshot 1`] = `
<div>
<main
class="center-column"
class="column"
>
<div
class="masthead"
>
<h1>
<h1
class="heading"
>
PrivacyPal
</h1>
<h2>
<h2
class="subheading"
>
COSC 499 Capstone Team 1
</h2>
</div>
Expand All @@ -28,6 +32,32 @@ exports[`Home homepage is unchanged from snapshot 1`] = `
>
Patternfly Button
</button>
<h2
class="subheading"
>
Test Login
</h2>
<p>
You can login with the username "
<code>
johnny@example.com
</code>
" and password "
<code>
password
</code>
"
</p>
<br />
<p>
You can visit
<a
href="/api/session"
>
this link
</a>
to see a breakdown of your current session.
</p>
</main>
</div>
`;
35 changes: 35 additions & 0 deletions app/front-end/__tests__/config.lib.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Created on Sun Oct 29 2023
* Author: Connor Doman
*/

import { extractConfigFile } from "@lib/config";

describe("Config Library Tests", () => {
it("extractConfigFile works for valid config file", async () => {
const config = await extractConfigFile("extractConfigFile.test.json", "./__tests__/test-configs");
expect(config).toEqual({
tests: [
{
id: 1,
name: "Test Extract Config File",
},
],
});
});

it("extractConfigFile works for invalid config file", async () => {
const config = await extractConfigFile("invalidConfigFile.test.json", "./__tests__/test-configs");
expect(config).toEqual({});
});

it("extractConfigFile works for non-existent config file", async () => {
const config = await extractConfigFile("nonExistentConfigFile.test.json", "./__tests__/test-configs");
expect(config).toEqual({});
});

it("extractConfigFile works for non-existent config directory", async () => {
const config = await extractConfigFile("nonExistentConfigFile.test.json", "./__tests__/non-existent-dir");
expect(config).toEqual({});
});
});
41 changes: 41 additions & 0 deletions app/front-end/__tests__/dummy-authenticator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Created on Sun Oct 29 2023
* Author: Connor Doman
*/

import { extractConfigFile } from "@lib/config";
import { DummyAuthenticator } from "@lib/dummy-authenticator";

describe("Dummy Authenticator", () => {
it("works for valid credentials", async () => {
const dummyAuthenticator = new DummyAuthenticator();
const credentials = { email: "johnny@example.com", password: "password" };
DummyAuthenticator.configDirectory = "./conf/";
const user = await dummyAuthenticator.authorize(credentials, {} as any);
expect(user).toEqual({
id: "1",
email: "johnny@example.com",
});
});

it("fails for invalid credentials", async () => {
const dummyAuthenticator = new DummyAuthenticator();
const credentials = { email: "johnny@example.com", password: "wrongpassword" };
DummyAuthenticator.configDirectory = "./conf/";
const user = await dummyAuthenticator.authorize(credentials, {} as any);
expect(user).toEqual(null);
});

it("contains 'credentials' profile name", () => {
const dummyAuthenticator = new DummyAuthenticator();
expect(dummyAuthenticator.name).toEqual("credentials");
});

it("contains correct credentials profile", () => {
const dummyAuthenticator = new DummyAuthenticator();
expect(dummyAuthenticator.credentials).toEqual({
email: { label: "Email", type: "text", placeholder: "Email" },
password: { label: "Password", type: "password" },
});
});
});
4 changes: 2 additions & 2 deletions app/front-end/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import "@testing-library/jest-dom";
/* Example Test: Does the page at `/` render a header? (Should FAIL) */
describe("Home", () => {
it("page has 'PrivacyPal' semantic heading", () => {
render(<Home />);
render(<Home useAuth={false} />);
expect(screen.getByRole("heading", { name: "PrivacyPal" })).toBeInTheDocument();
});

it("homepage is unchanged from snapshot", () => {
const { container } = render(<Home />);
const { container } = render(<Home useAuth={false} />);
expect(container).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"tests": [
{
"id": 1,
"name": "Test Extract Config File"
}
]
}
25 changes: 25 additions & 0 deletions app/front-end/conf/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# PrivacyPal Configuration

## Users

User data for basic authentication is stored in `./user.properties.json` in the form of:

```typescript
interface PrivacyPalUser {
id: string;
email: string;
password: string;
}

interface PrivacyPalUserProperties {
users: PrivacyPalUser[];
}
```

Where `password` is a base64 encoded `bcrypt` hash of the user's password:

```typescript
const password = btoa(await bcrypt.hash(userPassword, 10));
```

The sample file features a user with the email `johnny@example.com` and password `password`.
9 changes: 9 additions & 0 deletions app/front-end/conf/user.properties.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"users": [
{
"id": "1",
"email": "johnny@example.com",
"hashedPassword": "JDJiJDEwJDQwR0psSWo3Q08zcEhTVkpTVTNhbU9QQmZlYWl1dk1SL2dTMlZCclAuOGIuMjdiZFJTYjVp"
}
]
}
Loading
Loading