Open danielo515 opened 3 weeks ago
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.
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.
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!