[READ-ONLY] Mirror of https://github.com/thoda-dev/shhh. Self-hostable zero-knowledge pastebin for secrets that expire on their own
docker end-to-end-encryption nuxt nuxtjs pastebin secrets selft-hosted zero-knowledge
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778import { relations, sql } from 'drizzle-orm'import { pgTable, pgEnum, text, integer, boolean, timestamp, uuid, index, check, customType} from 'drizzle-orm/pg-core'import { users } from './user'import { pasteEmailRecipients } from './paste-email-recipient'
// Encrypted payloads are opaque to the server (zero-knowledge) — stored as raw bytes, never text/base64.const bytea = customType<{ data: Buffer }>({ dataType() { return 'bytea' }})
export const pasteKindEnum = pgEnum('paste_kind', ['text', 'file'])
// How the reader displays the decrypted text. The server never sees the content, so it cannot infer this — the creator declares it.export const pasteFormatEnum = pgEnum('paste_format', ['plain', 'markdown'])
export const pastes = pgTable( 'pastes', { id: uuid('id').defaultRandom().primaryKey(), ownerId: text('owner_id').references(() => users.id, { onDelete: 'cascade' }), kind: pasteKindEnum('kind').notNull(), format: pasteFormatEnum('format').notNull().default('plain'),
// kind = 'text' ciphertext: bytea('ciphertext'), iv: bytea('iv'),
// kind = 'file' fileBlob: bytea('file_blob'), fileIv: bytea('file_iv'), fileNameEnc: bytea('file_name_enc'), fileNameIv: bytea('file_name_iv'), fileMime: text('file_mime'), fileSize: integer('file_size'),
passwordProtected: boolean('password_protected').notNull().default(false),
// sha256 of the AES key, which the reader recomputes to spend a read. Nullable only because // pastes created before this existed have none; those keep the old behaviour until they expire, // which the retention cap bounds. See `deriveUnlockHash` in app/utils/crypto.ts. unlockHash: bytea('unlock_hash'), maxReads: integer('max_reads'), readCount: integer('read_count').notNull().default(0), expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), lastReadAt: timestamp('last_read_at', { withTimezone: true }) }, table => [ index('pastes_owner_id_idx').on(table.ownerId), index('pastes_expires_at_idx').on(table.expiresAt), check( 'pastes_kind_payload_check', sql`(${table.kind} = 'text' AND ${table.ciphertext} IS NOT NULL AND ${table.fileBlob} IS NULL) OR (${table.kind} = 'file' AND ${table.fileBlob} IS NOT NULL AND ${table.ciphertext} IS NULL)` ), // A file has no format: it is downloaded, never rendered. check('pastes_format_kind_check', sql`${table.format} = 'plain' OR ${table.kind} = 'text'`) ])
export const pastesRelations = relations(pastes, ({ one, many }) => ({ owner: one(users, { fields: [pastes.ownerId], references: [users.id] }), emailRecipients: many(pasteEmailRecipients)}))