Giter Site home page Giter Site logo

remix-auth-form's Introduction

Hi! I'm Sergio

Web Developer

Currently coding Daffy.org

Previously @prmkr, @platzi (YC W15), @vercel, and @able_co.

Organizer of @techtalks_pe.

Repos are my own.

remix-auth-form's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

remix-auth-form's Issues

Separating AuthorizationError from other errors within the strategy?

Hey Sergio!

Given this action:

export async function action({ request }: ActionArgs) {
  try {
    const form = await request.formData();

    validateEmail(form.get("email"));
    validatePasswordAndConfirmation(form.get("password"), form.get("password"));

    await authenticator.authenticate("user-pass", request, {
      throwOnError: true,
      context: { formData: form },
      successRedirect: "/profile",
    });
  } catch (error) {
    if (error instanceof AuthorizationError) {
      return json({ error: errorForToast(error.message) }, { status: 401 });
    }

    // Because redirects work by throwing a Response, it needs to be re-thrown.
    // Any other generic errors must also be thrown too.
    //
    // The "instanceof Response" line is added here for clarity, but "throw error"
    // would cover it already.
    if (error instanceof Response) throw error;
    throw error;
  }
}

If any kind of error is thrown internally, such Prisma throwing that the database is down, (

if (error instanceof Error) {
), it's sent to this.failure, and then it gets wrapped in AuthorizationError by remix-auth here: https://github.com/sergiodxa/remix-auth/blob/d4e4f85745b13b0bbbb08bdd12375a91de48fd84/src/strategy.ts#LL125C35-L125C35

So effectively a prisma Error ends up being an AuthorizationError so it's treated no differently from user errors.
image

How can I determine what's an "AuthenticationError" such as wrong username, or password, which I'm doing by specifically throwing that error from within my Authenticator code [1], so that it's shown to users, versus internal server errors, that must not be displayed and should instead return a 500 or be caught by the CatchBoundary?

In my remix-auth-form strategy I'm specifically throwing AuthorizationError, but the way error handling is done it seems to cast a wider net:

export const authUserWithEmailAndPassword = async ({
  inputEmail,
  inputPassword,
}: loginUserArgs): Promise<User> => {
  const blacklisted = await existsByDomainOrEmail(inputEmail);
  if (blacklisted) {
    throw new AuthorizationError(
      "Sorry, your account has been suspended for breaching our Terms of Service."
    );
  }

  const user = await getUserByEmail(inputEmail);
  if (!user || !user.password) {
    throw new AuthorizationError("Incorrect email or password");
  }

  const passwordMatches = await bcrypt.compare(inputPassword, user.password);

  if (!passwordMatches) {
    await incrementFailedLogins(user);
    throw new AuthorizationError("Incorrect email or password");
  }

  await createLoginRecord({ userId: user.id, email: inputEmail });

  return user;

request.formData is undefined

remix-auth: 3.2.1
remix-auth-form: 1.1.1

after configuring everything and hitting the login button I get an error saying request.formData is not a function. I tranced it back to the request.clone() found in authenticator.js:88
return strategyObj.authenticate(request.clone(), this.sessionStorage, {...
removing the .clone() and I get no error.
could it be that cloning removed the formData ?

AppLoadContext type seems to not match how it is designed to work

I am trying to work out how to get some arbitrary data into the form strategy as a means of creating an history of sign-in but the type on the context object seems strange.

export async function action({ context, request }: ActionArgs) {
  return await authenticator.authenticate("form", request, {
    successRedirect: "/",
    failureRedirect: "/login",
    context, // optional
  });
};

on this one, you are passing the remix context - which is all good.

but then you also override it with formData on the next one.

export async function action({ context, request }: ActionArgs) {
  let formData = await request.formData();
  return await authenticator.authenticate("form", request, {
    // use formData here
    successRedirect: formData.get("redirectTo"),
    failureRedirect: "/login",
    context: { formData }, // pass pre-read formData here
  });
};

but it should still be typed as AppLoadContext, It seems like this type might want to be generic or somehow user customizable? is that a fair assumption?

(ps happy to submit a pr if worthwhile)

Feature: Bundle a hashing function

Would it be worth providing a small hash function to the authenticate method?

This adds a bit of opinion about the right way to store passwords, but in this case I think a bit of well educated opinion would go a long way toward encouraging good security practices with a well chosen hash, like PBKDF2 which crypto-js supports (and is Cloudflare workers compliant)

authenticate({ form, hash }) { }

How to pass translation i18next helper to FormStrategy

Hi,
trying to pass t helper for yup validation inside FormStrategy but it fails.
What is the right way to do it?

import { useTranslation } from "react-i18next";
export let authenticator = new Authenticator<User>(sessionStorage, {
  sessionErrorKey: "sessionErrorKey"
});
authenticator.use(
    new FormStrategy(async ({ form }) => {
      const { t } = useTranslation('common');
      const email = form.get('email') as string;
      const password = form.get('password') as string;
      const validationResult = await userValidationSchema(t).validate(Object.fromEntries(form), {abortEarly: true}).catch((err) => err);
      if(validationResult.errors){
        throw new AuthorizationError(validationResult.errors[0]);
      }
      const user = await validateUser(email, password);
      // console.log('user', user);
      if (user !== null) {
        return await Promise.resolve({ ...user as User});
      } else {
        // if problem with user throw error AuthorizationError
        throw new AuthorizationError("Bad Credentials")
      }
    }),

  );

BUG: request.formData is not a function

Description

I get request.formData is not a function error when I submit my login form

// auth.server.ts
import { Authenticator } from "remix-auth"
import { FormStrategy } from "remix-auth-form"

import type { User } from "@prisma/client"

import { loginSchema } from "~/validation"
import { sessionStorage } from "~/services/session.server"

export const authenticator = new Authenticator<User>(sessionStorage)

authenticator.use(
	new FormStrategy(async ({ form }) => {
		const userInput = Object.fromEntries(form)
		const { email, password } = await loginSchema.validate(userInput)
		let user = await login(email, password)
		return user
	}),
	"form"
)
// routes/login.tsx
import type { VFC } from "react"
import type { ActionFunction } from "remix"

import { Form } from "remix"
import { authenticator } from "~/services/auth.server"

export const action: ActionFunction = async ({ request }) => {
	await authenticator.authenticate("form", request, {
		failureRedirect: "/login",
		successRedirect: "/",
	})
	return {}
}


const Login: VFC = () => {
	return (
		<Form method="post">
			<label>
				email
				<input type="email" name="email" />
			</label>
			<label>
				password
				<input type="password" name="password" />
			</label>
			<button type="submit">Login</button>
		</Form>
	)
}

export default Login

More Info about packages and installed version

"react": "^17.0.2",
"react-dom": "^17.0.2",
"remix": "^1.0.6",
"remix-auth": "^2.5.0-0",
"remix-auth-form": "^1.1.1",
"@remix-run/dev": "^1.0.6",
"@remix-run/react": "^1.0.6",
"@remix-run/serve": "^1.0.6",

Add support for "remember" option.

I don't see any way to add the ability for a user to check a "remember me" box on the form to set the maxAge value of the session to something like 30 days.

In Remix-land, this would be done with a session commit like this:

await sessionStorage.commitSession(session, {
  maxAge: remember
    ? 60 * 60 * 24 * 7 // 7 days
    : undefined,
}),

But Remix-Auth doesn't seem to have a way to provide an "options" object that can be passed to the commitSession call.

Check and refresh session

With the current solution an user is authenticated as long as a valid session cookie is provided. What I miss is a checkSession function like in remix-auth-supabase to validate the user against the database while he still has a valid session cookie. When checkSession is called successfully we can also set the session cookie again to keep the user logged in.

How do you like the idea? I would be open to help with the implementation.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.