UNPKG

@directus/api

Version:

Directus is a real-time API and App dashboard for managing SQL database content

136 lines (132 loc) 4.72 kB
import { useLogger } from "../logger/index.js"; import { getMilliseconds } from "../utils/get-milliseconds.js"; import { clearCache } from "../permissions/cache.js"; import { validateAccess } from "../permissions/modules/validate-access/validate-access.js"; import { getSecret } from "../utils/get-secret.js"; import { ItemsService } from "./items.js"; import { Url } from "../utils/url.js"; import { userName } from "../utils/user-name.js"; import { md } from "../utils/md.js"; import { MailService } from "./mail/index.js"; import { UsersService } from "./users.js"; import { useEnv } from "@directus/env"; import { ForbiddenError, InvalidCredentialsError } from "@directus/errors"; import { nanoid } from "nanoid"; import argon2 from "argon2"; import jwt from "jsonwebtoken"; //#region src/services/shares.ts const env = useEnv(); const logger = useLogger(); var SharesService = class extends ItemsService { constructor(options) { super("directus_shares", options); } async createOne(data, opts) { if (this.accountability) await validateAccess({ accountability: this.accountability, action: "share", collection: data["collection"], primaryKeys: [data["item"]] }, { schema: this.schema, knex: this.knex }); return super.createOne(data, opts); } async updateMany(keys, data, opts) { const primaryKeys = await super.updateMany(keys, data, opts); await clearCache(); return primaryKeys; } async deleteMany(keys, opts) { const primaryKeys = await super.deleteMany(keys, opts); await clearCache(); return primaryKeys; } async login(payload, options) { const record = await this.knex.select({ share_id: "id", share_start: "date_start", share_end: "date_end", share_times_used: "times_used", share_max_uses: "max_uses", share_password: "password" }).from("directus_shares").where("id", payload["share"]).andWhere((subQuery) => { subQuery.whereNull("date_end").orWhere("date_end", ">=", /* @__PURE__ */ new Date()); }).andWhere((subQuery) => { subQuery.whereNull("date_start").orWhere("date_start", "<=", /* @__PURE__ */ new Date()); }).andWhere((subQuery) => { subQuery.whereNull("max_uses").orWhere("max_uses", ">=", this.knex.ref("times_used")); }).first(); if (!record) throw new InvalidCredentialsError(); if (record.share_password && !await argon2.verify(record.share_password, payload["password"])) throw new InvalidCredentialsError(); await this.knex("directus_shares").update({ times_used: record.share_times_used + 1 }).where("id", record.share_id); const tokenPayload = { app_access: false, admin_access: false, role: null, share: record.share_id }; const refreshToken = nanoid(64); const refreshTokenExpiration = new Date(Date.now() + getMilliseconds(env["REFRESH_TOKEN_TTL"], 0)); if (options?.session) tokenPayload.session = refreshToken; const TTL = env[options?.session ? "SESSION_COOKIE_TTL" : "ACCESS_TOKEN_TTL"]; const accessToken = jwt.sign(tokenPayload, getSecret(), { expiresIn: TTL, issuer: "directus" }); await this.knex("directus_sessions").insert({ token: refreshToken, expires: refreshTokenExpiration, ip: this.accountability?.ip, user_agent: this.accountability?.userAgent, origin: this.accountability?.origin, share: record.share_id }); await this.knex("directus_sessions").delete().where("expires", "<", /* @__PURE__ */ new Date()); return { accessToken, refreshToken, expires: getMilliseconds(TTL) }; } /** * Send a link to the given share ID to the given email(s). Note: you can only send a link to a share * if you have read access to that particular share */ async invite(payload) { if (!this.accountability?.user) throw new ForbiddenError(); const share = await this.readOne(payload.share, { fields: ["collection"] }); const usersService = new UsersService({ knex: this.knex, schema: this.schema }); const mailService = new MailService({ schema: this.schema, accountability: this.accountability }); const userInfo = await usersService.readOne(this.accountability.user, { fields: [ "first_name", "last_name", "email", "id" ] }); const message = ` Hello! ${userName(userInfo)} has invited you to view an item in ${share["collection"]}. [Open](${new Url(env["PUBLIC_URL"]).addPath("admin", "shared", payload.share).toString()}) `; for (const email of payload.emails) mailService.send({ template: { name: "base", data: { html: md(message) } }, to: email, subject: `${userName(userInfo)} has shared an item with you` }).catch((error) => { logger.error(error, `Could not send share notification mail`); }); } }; //#endregion export { SharesService };