Skip to content

Commit

Permalink
Upgrade to Remix Auth v4 (#57)
Browse files Browse the repository at this point in the history
Updated strategy to use [remix-auth
v4](sergiodxa/remix-auth#299)

---------

Co-authored-by: Sergio Xalambrí <hello@sergiodxa.com>
  • Loading branch information
nolannbiron and sergiodxa authored Nov 27, 2024
1 parent b502fa5 commit 6ddd69e
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 124 deletions.
Binary file modified bun.lockb
Binary file not shown.
6 changes: 2 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,14 @@
"./package.json": "./package.json"
},
"peerDependencies": {
"@remix-run/server-runtime": "^1.0.0 || ^2.0.0",
"remix-auth": "^3.6.0"
"remix-auth": "^4.0.0"
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.15.3",
"@biomejs/biome": "^1.7.2",
"@remix-run/node": "^2.9.2",
"@types/bun": "^1.1.1",
"consola": "^3.2.3",
"remix-auth": "^3.7.0",
"remix-auth": "^4.0.0",
"typedoc": "^0.25.13",
"typedoc-plugin-mdn-links": "^3.1.23",
"typescript": "^5.4.5"
Expand Down
70 changes: 11 additions & 59 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,8 @@
import { beforeEach, expect, mock, test } from "bun:test";
import { createCookieSessionStorage } from "@remix-run/node";
import { AuthenticateOptions, AuthorizationError } from "remix-auth";
import { FormStrategy, FormStrategyVerifyParams } from ".";

let verify = mock();

let sessionStorage = createCookieSessionStorage({
cookie: { secrets: ["s3cr3t"] },
});

let options = {
name: "form",
sessionKey: "user",
sessionErrorKey: "error",
sessionStrategyKey: "strategy",
} satisfies AuthenticateOptions;

beforeEach(() => {
verify.mockReset();
});
Expand All @@ -33,7 +20,7 @@ test("should pass to the verify callback a FormData object", async () => {

let strategy = new FormStrategy(verify);

await strategy.authenticate(request, sessionStorage, options);
await strategy.authenticate(request);

expect(verify).toBeCalledWith({ form: body, request });
});
Expand All @@ -50,29 +37,11 @@ test("should return what the verify callback returned", async () => {

let strategy = new FormStrategy<string>(verify);

let user = await strategy.authenticate(request, sessionStorage, options);
let user = await strategy.authenticate(request);

expect(user).toBe("test@example.com");
});

test("should pass the context to the verify callback", async () => {
let body = new FormData();
body.set("email", "test@example.com");

let request = new Request("http://.../test", { body, method: "POST" });

let context = { test: "it works!" };

let strategy = new FormStrategy(verify);

await strategy.authenticate(request, sessionStorage, {
...options,
context,
});

expect(verify).toBeCalledWith({ form: body, context, request });
});

test("should pass error as cause on failure", async () => {
verify.mockImplementationOnce(() => {
throw new TypeError("Invalid email address");
Expand All @@ -85,15 +54,10 @@ test("should pass error as cause on failure", async () => {

let strategy = new FormStrategy(verify);

let result = await strategy
.authenticate(request, sessionStorage, {
...options,
throwOnError: true,
})
.catch((error) => error);
let result = await strategy.authenticate(request).catch((error) => error);

expect(result).toEqual(new AuthorizationError("Invalid email address"));
expect((result as AuthorizationError).cause).toEqual(
expect(result).toEqual(new Error("Invalid email address"));

Check failure on line 59 in src/index.test.ts

View workflow job for this annotation

GitHub Actions / Tests

error: expect(received).toEqual(expected)

+ 42 | expect(user).toBe("test@example.com"); + 43 | }); + 44 | + 45 | test("should pass error as cause on failure", async () => { + 46 | verify.mockImplementationOnce(() => { + 47 | throw new TypeError("Invalid email address"); + ^ + TypeError: Invalid email address + at /home/runner/work/remix-auth-form/remix-auth-form/src/index.test.ts:47:9 - 54 | - 55 | let strategy = new FormStrategy(verify); - 56 | - 57 | let result = await strategy.authenticate(request).catch((error) => error); - 58 | - 59 | expect(result).toEqual(new Error("Invalid email address")); - ^ - error: Invalid email address - at /home/runner/work/remix-auth-form/remix-auth-form/src/index.test.ts:59:25 - Expected - 9 + Received + 9 at /home/runner/work/remix-auth-form/remix-auth-form/src/index.test.ts:59:17
expect((result as Error).cause).toEqual(
new TypeError("Invalid email address"),
);
});
Expand All @@ -113,17 +77,10 @@ test("should pass generate error from string on failure", async () => {

let strategy = new FormStrategy(verify);

let result = await strategy
.authenticate(request, sessionStorage, {
...options,
throwOnError: true,
})
.catch((error) => error);
let result = await strategy.authenticate(request).catch((error) => error);

expect(result).toEqual(new AuthorizationError("Invalid email address"));
expect((result as AuthorizationError).cause).toEqual(
new Error("Invalid email address"),
);
expect(result).toEqual(new Error("Invalid email address"));

Check failure on line 82 in src/index.test.ts

View workflow job for this annotation

GitHub Actions / Tests

error: expect(received).toEqual(expected)

Expected: 77 | 78 | let strategy = new FormStrategy(verify); 79 | 80 | let result = await strategy.authenticate(request).catch((error) => error); 81 | 82 | expect(result).toEqual(new Error("Invalid email address")); ^ error: Invalid email address at /home/runner/work/remix-auth-form/remix-auth-form/src/index.test.ts:82:25 Received: "Invalid email address" at /home/runner/work/remix-auth-form/remix-auth-form/src/index.test.ts:82:17
expect((result as Error).cause).toEqual(new Error("Invalid email address"));
});

test("should create Unknown error if thrown value is not Error or string", async () => {
Expand All @@ -138,15 +95,10 @@ test("should create Unknown error if thrown value is not Error or string", async

let strategy = new FormStrategy(verify);

let result = await strategy
.authenticate(request, sessionStorage, {
...options,
throwOnError: true,
})
.catch((error) => error);
let result = await strategy.authenticate(request).catch((error) => error);

expect(result).toEqual(new AuthorizationError("Unknown error"));
expect((result as AuthorizationError).cause).toEqual(
expect(result).toEqual(new Error("Unknown error"));

Check failure on line 100 in src/index.test.ts

View workflow job for this annotation

GitHub Actions / Tests

error: expect(received).toEqual(expected)

+ { + message: "Invalid email address", + } - 95 | - 96 | let strategy = new FormStrategy(verify); - 97 | - 98 | let result = await strategy.authenticate(request).catch((error) => error); - 99 | - 100 | expect(result).toEqual(new Error("Unknown error")); - ^ - error: Unknown error - at /home/runner/work/remix-auth-form/remix-auth-form/src/index.test.ts:100:25 - Expected - 9 + Received + 3 at /home/runner/work/remix-auth-form/remix-auth-form/src/index.test.ts:100:17
expect((result as Error).cause).toEqual(
new Error(JSON.stringify({ message: "Invalid email address" }, null, 2)),
);
});
83 changes: 22 additions & 61 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,15 @@
import type { AppLoadContext, SessionStorage } from "@remix-run/server-runtime";
import { AuthenticateOptions, Strategy } from "remix-auth";

export interface FormStrategyVerifyParams {
/**
* A FormData object with the content of the form used to trigger the
* authentication.
*
* Here you can read any input value using the FormData API.
*/
form: FormData;
/**
* An object of arbitrary for route loaders and actions provided by the
* server's `getLoadContext()` function.
*/
context?: AppLoadContext;
/**
* The request that triggered the authentication.
*/
request: Request;
}
import { Strategy } from "remix-auth/strategy";

export class FormStrategy<User> extends Strategy<
User,
FormStrategyVerifyParams
FormStrategy.VerifyOptions
> {
name = "form";

async authenticate(
request: Request,
sessionStorage: SessionStorage,
options: AuthenticateOptions,
): Promise<User> {
try {
if (request.bodyUsed) throw new BodyUsedError();
let form = await request.clone().formData();
let user = await this.verify({ form, context: options.context, request });
return this.success(user, request, sessionStorage, options);
} catch (error) {
if (error instanceof Error) {
return await this.failure(
error.message,
request,
sessionStorage,
options,
error,
);
}

if (typeof error === "string") {
return await this.failure(
error,
request,
sessionStorage,
options,
new Error(error),
);
}

return await this.failure(
"Unknown error",
request,
sessionStorage,
options,
new Error(JSON.stringify(error, null, 2)),
);
}
async authenticate(request: Request): Promise<User> {
if (request.bodyUsed) throw new BodyUsedError();
let form = await request.clone().formData();
return this.verify({ form, request });
}
}

Expand Down Expand Up @@ -98,3 +43,19 @@ export class BodyUsedError extends Error {
);
}
}

export namespace FormStrategy {
export interface VerifyOptions {
/**
* A FormData object with the content of the form used to trigger the
* authentication.
*
* Here you can read any input value using the FormData API.
*/
form: FormData;
/**
* The request that triggered the authentication.
*/
request: Request;
}
}

0 comments on commit 6ddd69e

Please sign in to comment.