UNPKG

alinea

Version:

[![npm](https://img.shields.io/npm/v/alinea.svg)](https://npmjs.org/package/alinea) [![install size](https://packagephobia.com/badge?p=alinea)](https://packagephobia.com/result?p=alinea)

330 lines (326 loc) 9.61 kB
import "../../chunks/chunk-U5RRZUYZ.js"; // src/auth/passwordless/PasswordLessAuth.server.ts import { router } from "alinea/backend/router/Router"; import { Connection, HttpError, Outcome } from "alinea/core"; import { sign, verify } from "alinea/core/util/JWT"; // node_modules/superstruct/dist/index.mjs var StructError = class extends TypeError { constructor(failure, failures) { let cached; const { message, explanation, ...rest } = failure; const { path } = failure; const msg = path.length === 0 ? message : `At path: ${path.join(".")} -- ${message}`; super(explanation ?? msg); if (explanation != null) this.cause = msg; Object.assign(this, rest); this.name = this.constructor.name; this.failures = () => { return cached ?? (cached = [failure, ...failures()]); }; } }; function isIterable(x) { return isObject(x) && typeof x[Symbol.iterator] === "function"; } function isObject(x) { return typeof x === "object" && x != null; } function print(value) { if (typeof value === "symbol") { return value.toString(); } return typeof value === "string" ? JSON.stringify(value) : `${value}`; } function shiftIterator(input) { const { done, value } = input.next(); return done ? void 0 : value; } function toFailure(result, context, struct, value) { if (result === true) { return; } else if (result === false) { result = {}; } else if (typeof result === "string") { result = { message: result }; } const { path, branch } = context; const { type } = struct; const { refinement, message = `Expected a value of type \`${type}\`${refinement ? ` with refinement \`${refinement}\`` : ""}, but received: \`${print(value)}\`` } = result; return { value, type, refinement, key: path[path.length - 1], path, branch, ...result, message }; } function* toFailures(result, context, struct, value) { if (!isIterable(result)) { result = [result]; } for (const r of result) { const failure = toFailure(r, context, struct, value); if (failure) { yield failure; } } } function* run(value, struct, options = {}) { const { path = [], branch = [value], coerce = false, mask: mask2 = false } = options; const ctx = { path, branch }; if (coerce) { value = struct.coercer(value, ctx); if (mask2 && struct.type !== "type" && isObject(struct.schema) && isObject(value) && !Array.isArray(value)) { for (const key in value) { if (struct.schema[key] === void 0) { delete value[key]; } } } } let status = "valid"; for (const failure of struct.validator(value, ctx)) { failure.explanation = options.message; status = "not_valid"; yield [failure, void 0]; } for (let [k, v, s] of struct.entries(value, ctx)) { const ts = run(v, s, { path: k === void 0 ? path : [...path, k], branch: k === void 0 ? branch : [...branch, v], coerce, mask: mask2, message: options.message }); for (const t of ts) { if (t[0]) { status = t[0].refinement != null ? "not_refined" : "not_valid"; yield [t[0], void 0]; } else if (coerce) { v = t[1]; if (k === void 0) { value = v; } else if (value instanceof Map) { value.set(k, v); } else if (value instanceof Set) { value.add(v); } else if (isObject(value)) { if (v !== void 0 || k in value) value[k] = v; } } } } if (status !== "not_valid") { for (const failure of struct.refiner(value, ctx)) { failure.explanation = options.message; status = "not_refined"; yield [failure, void 0]; } } if (status === "valid") { yield [void 0, value]; } } var Struct = class { constructor(props) { const { type, schema, validator, refiner, coercer = (value) => value, entries = function* () { } } = props; this.type = type; this.schema = schema; this.entries = entries; this.coercer = coercer; if (validator) { this.validator = (value, context) => { const result = validator(value, context); return toFailures(result, context, this, value); }; } else { this.validator = () => []; } if (refiner) { this.refiner = (value, context) => { const result = refiner(value, context); return toFailures(result, context, this, value); }; } else { this.refiner = () => []; } } /** * Assert that a value passes the struct's validation, throwing if it doesn't. */ assert(value, message) { return assert(value, this, message); } /** * Create a value with the struct's coercion logic, then validate it. */ create(value, message) { return create(value, this, message); } /** * Check if a value passes the struct's validation. */ is(value) { return is(value, this); } /** * Mask a value, coercing and validating it, but returning only the subset of * properties defined by the struct's schema. */ mask(value, message) { return mask(value, this, message); } /** * Validate a value with the struct's validation logic, returning a tuple * representing the result. * * You may optionally pass `true` for the `withCoercion` argument to coerce * the value before attempting to validate it. If you do, the result will * contain the coerced result when successful. */ validate(value, options = {}) { return validate(value, this, options); } }; function assert(value, struct, message) { const result = validate(value, struct, { message }); if (result[0]) { throw result[0]; } } function create(value, struct, message) { const result = validate(value, struct, { coerce: true, message }); if (result[0]) { throw result[0]; } else { return result[1]; } } function mask(value, struct, message) { const result = validate(value, struct, { coerce: true, mask: true, message }); if (result[0]) { throw result[0]; } else { return result[1]; } } function is(value, struct) { const result = validate(value, struct); return !result[0]; } function validate(value, struct, options = {}) { const tuples = run(value, struct, options); const tuple = shiftIterator(tuples); if (tuple[0]) { const error = new StructError(tuple[0], function* () { for (const t of tuples) { if (t[0]) { yield t[0]; } } }); return [error, void 0]; } else { const v = tuple[1]; return [void 0, v]; } } function define(name, validator) { return new Struct({ type: name, schema: null, validator }); } function never() { return define("never", () => false); } function object(schema) { const knowns = schema ? Object.keys(schema) : []; const Never = never(); return new Struct({ type: "object", schema: schema ? schema : null, *entries(value) { if (schema && isObject(value)) { const unknowns = new Set(Object.keys(value)); for (const key of knowns) { unknowns.delete(key); yield [key, value[key], schema[key]]; } for (const key of unknowns) { yield [key, value[key], Never]; } } }, validator(value) { return isObject(value) || `Expected an object, but received: ${print(value)}`; }, coercer(value) { return isObject(value) ? { ...value } : value; } }); } function string() { return define("string", (value) => { return typeof value === "string" || `Expected a string, but received: ${print(value)}`; }); } // src/auth/passwordless/PasswordLessAuth.server.ts var LoginBody = object({ email: string() }); var PasswordLessAuth = class { constructor(options) { this.options = options; const matcher = router.startAt(Connection.routes.base); this.router = router( matcher.post(Connection.routes.base + "/auth.passwordless").map(router.parseJson).map(async ({ body }) => { assert(body, LoginBody); const email = body.email; const isUser = await this.options.isUser(email); if (!isUser) return Outcome.Failure(new HttpError(404, "User not found")); const token = await sign({ sub: email }, this.options.jwtSecret); const url = `${this.options.dashboardUrl}?token=${token}`; await this.options.transporter.sendMail({ from: this.options.from, to: body.email, subject: this.options.subject, text: url }); return Outcome.Success("Sent"); }).map(router.jsonResponse), router.use(async (request) => { try { const user = await this.userFor(request); } catch (e) { return Outcome.Failure(new HttpError(401, "Unauthorized")); } }).map(router.jsonResponse) ).recover(router.reportError); } router; users = /* @__PURE__ */ new WeakMap(); async contextFor(request) { return { user: await this.userFor(request) }; } async userFor(request) { if (this.users.has(request)) return this.users.get(request); const authHeader = request.headers.get("Authorization"); if (!authHeader) throw new HttpError(400, "No Authorization header"); const [scheme, token] = authHeader.split(" "); if (scheme !== "Bearer") throw new HttpError(400, "Invalid Authorization header"); const user = await verify(token, this.options.jwtSecret); this.users.set(request, user); return user; } }; export { PasswordLessAuth };