alinea
Version:
[](https://npmjs.org/package/alinea) [](https://packagephobia.com/result?p=alinea)
330 lines (326 loc) • 9.61 kB
JavaScript
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
};