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

Lucia cookie example? #665

Open
danielo515 opened this issue Aug 19, 2024 · 2 comments
Open

Lucia cookie example? #665

danielo515 opened this issue Aug 19, 2024 · 2 comments

Comments

@danielo515
Copy link

Hello again, donyou have an example of how to use Lucia with effect-http? I want to set the values Lucia returns from the createCookie method, wich has a name, value and attributes. Http platform has a function that almost marches this signature, but I don't know how to make it work with effect-http.
I also saw a todo comment on the security for the cookie auth, so an example for that will also be very appreciated.

Thank you!

@danielo515
Copy link
Author

danielo515 commented Aug 20, 2024

This is what I'm doing, and it seems to work. Not sure if it is the right approach.

This is my api-definition for the login endpoint:

  Api.addEndpoint(
    pipe(
      Api.post("login", "/api/login"),
      Api.setRequestBody(Schema.Array(ExternalExpense)),
      Api.setRequestBody(LoginPayload),
      Api.setResponseHeaders(
        Schema.Struct({
          "Set-Cookie": Schema.String,
        })
      )
    )
  )

And here is the handler:

  RouterBuilder.handle("login", ({ body: { email, password } }) =>
    Effect.gen(function* (_) {
      const user = yield* UserService.login(email, password);
      yield* Effect.logDebug("User logged in: ", user);
      const lucia = yield* CookieService;
      const session = yield* lucia.getUserSessions(user.id).pipe(
        Effect.tap((sessions) =>
          Effect.logDebug("User has sessions: ", sessions.length)
        ),
        Effect.flatMap(A.head),
        Effect.mapError(() => lucia.createSession(user.id, user))
      );

      const sessionCookie = lucia.createSessionCookie(session.id);

      return yield* Effect.succeed({
        status: 200,
        headers: { "Set-Cookie": sessionCookie.serialize() },
      } as const);
    }).pipe(
      Effect.catchTags({
        UserNotFound: () =>
          Effect.gen(function* (_) {
            yield* Effect.logWarning("User not found", { email });
            return yield* HttpError.unauthorized("Unauthorized");
          }),
        InvalidPassword: () =>
          Effect.gen(function* (_) {
            yield* Effect.logWarning("Invalid password", { email });
            return yield* HttpError.unauthorized("Unauthorized");
          }),
      }),
      Effect.withLogSpan("/api/login")
    )
  ),
  RouterBuilder.build
);

The CookieService is just a wrapper around lucia.

@danielo515
Copy link
Author

danielo515 commented Aug 22, 2024

So, here is my approach to the cookie security:

const cookie = pipe(
  HttpServerRequest.schemaCookies(
    Schema.Struct({ auth_session: Schema.String })
  ),
  Effect.mapError(() =>
    HttpError.unauthorized('Expected valid "auth_session" header')
  ),
  Effect.flatMap((obj) =>
    Effect.gen(function* (_) {
      const x = yield* HttpServerRequest.schemaHeaders(
        Schema.Struct({ origin: Schema.String, host: Schema.String })
      );

      const cookieService = yield* CookieService;
      console.log(verifyRequestOrigin(x.origin, [x.host]));
      yield* Effect.logDebug("Handling cookie security");
      const session = yield* cookieService.validateSession(obj.auth_session);
      if (!session.session) {
        yield* Effect.logDebug("Invalid or expired session, clearing cookie");
        return yield* HttpError.unauthorized("Unauthorized", {
          headers: pipe(
            Headers.empty,
            Headers.set(
              "Set-Cookie",
              cookieService.lucia.createBlankSessionCookie().serialize()
            )
          ),
        });
      }
      if (session.session.fresh) {
        /*   
        TODO: handle fresh session
         return HttpServerResponse.empty().pipe(
          HttpServerResponse.setCookie("session", session.session.id)
        );
        */
      }
      return session.user;
    })
  )
);

export const cookieSecurity = Security.make(cookie, {
  session: {
    name: "auth_session",
    type: "cookie",
    in: "cookie",
  },
});

As you can see, I'm still missing the ability to intercept the response and set additional headers to update the session cookie in case is needed. Is this a limitation of how security is supposed to be used?
This looks like a job for a middleware, but security doesn't seem to have such capability.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant