ttpos / url

A simple and efficient URL shortener
https://t.a.app/
0 stars 0 forks source link

用户部分: 用户数据库设计 #18

Closed aazf closed 1 month ago

aazf commented 2 months ago

数据表描述及功能

users 表

users_oidc 表

mfa 表

session 表

session_history 表

activity_log 表

cd996 commented 2 months ago

是否需要

users

session

aazf commented 2 months ago

tokens 表

cd996 commented 2 months ago

tokens 表

  • 描述: 存储用户生成的token,用于API操作
  • 字段:
    id              - 用户唯一标识
    user_id   - 用户ID
    token           - 手机号码
    created         - 创建日期
    updated         - 更新日期
    expriy_at        - 过期时间
    ip_address       - api授权的IP地址
    resources        - token 可以访问的资源
    last_used_at    - 最后一次访问时间

存储这个信息用于记录的目的吗? 但按常规操作,通过程序我们拿到 token 只会验证而已,而不是存储它。 存储 token 会不会有点敏感,安全风险。

如果不存储,我们只需要写入 redis, 只是负责读写操作而已,会不会更简单呢

cd996 commented 2 months ago

改动点

1. users 表

2. 新的表

3. mfa 表

4. 字段类型

schema.ts

import { integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core'

export const userTable = sqliteTable('user', {
  id: text('id').primaryKey(),
  email: text('email').unique(),
  emailHash: text('email_hash').notNull(),
  phone: text('phone').unique(),
  phoneHash: text('phone_hash').notNull(),
  password: text('password').notNull(),
  isEmailVerified: integer('is_email_verified').default(0),
  isPhoneVerified: integer('is_phone_verified').default(0),
  /**
   * 状态
   * - 0 禁用
   * - 1 激活
   */
  status: integer('status').default(0),
  created: integer('created_at').default(Date.now()),
  updated: integer('updated_at').default(Date.now()),
  nickname: text('nickname'),
  language: text('language'),
  country: text('country'),
}, (table) => {
  return {
    emailIndex: uniqueIndex('email_index').on(table.email),
    phoneIndex: uniqueIndex('phone_index').on(table.phone),
  }
})

export const emailVerificationTable = sqliteTable(
  'email_verification',
  {
    id: text('id').primaryKey(),
    userId: text('user_id')
      .notNull()
      .references(() => userTable.id),
    code: text('code').notNull(),
    expiresAt: integer('expires_at').notNull(),
    ip: text('ip_address').notNull(),
  },
  table => ({
    index: uniqueIndex('idx_email_verification_userId_code').on(table.userId, table.code),
  }),
)

export const passwordResetTable = sqliteTable(
  'password_reset',
  {
    id: text('id').primaryKey(),
    userId: text('user_id')
      .notNull()
      .references(() => userTable.id),
    code: text('code').notNull(),
    expiresAt: integer('expires_at').notNull(),
  },
  table => ({
    index: uniqueIndex('idx_password_reset_userId_code').on(table.userId, table.code),
  }),
)

export const usersOauthTable = sqliteTable('users_oauth', {
  id: text('id').primaryKey(),
  userId: text('user_id')
    .notNull()
    .references(() => userTable.id),
  provider: text('provider').notNull(),
  providerUserId: text('provider_user_id').notNull(),
  accessToken: text('access_token').notNull(),
  refreshToken: text('refresh_token'),
  expiresAt: integer('expires_at'),
  created: integer('created_at').default(Date.now()),
  updated: integer('updated_at').default(Date.now()),
}, (table) => {
  return {
    uniqueProviderUserIndex: uniqueIndex('unique_provider_user_index').on(table.provider, table.providerUserId),
  }
})

export const mfaTable = sqliteTable('mfa', {
  id: text('id').primaryKey(),
  userId: text('user_id')
    .notNull()
    .references(() => userTable.id),
  type: text('type').notNull(),
  name: text('name').notNull(),
  status: text('status').notNull(),
  lastVerified: integer('last_verified_at'),
  lastMfaId: text('last_mfa_id'),
  created: integer('created_at').default(Date.now()),
  updated: integer('updated_at').default(Date.now()),
})

export const sessionTable = sqliteTable('session', {
  id: text('id').primaryKey(),
  userId: text('user_id')
    .notNull()
    .references(() => userTable.id),
  sessionToken: text('session_token').notNull(),
  created: integer('created_at').default(Date.now()),
  updated: integer('updated_at').default(Date.now()),
  expiresAt: integer('expires_at').notNull(),
  status: text('status').notNull(),
  date: integer('date').default(Date.now()),
  ip: text('ip_address').notNull(),
  country: text('country'),
  deviceInfo: text('device_info'),
})

export const activityLogTable = sqliteTable('activity_log', {
  id: text('id').primaryKey(),
  userId: text('user_id')
    .notNull()
    .references(() => userTable.id),
  action: text('action').notNull(),
  timestamp: integer('timestamp').default(Date.now()),
  ipAddress: text('ip_address').notNull(),
  details: text('details'),
  sessionId: text('session_id'),
})
cd996 commented 2 months ago

最新 schema.ts 同步

/* eslint-disable ts/no-use-before-define */

import { blob, integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core'

export const userTable = sqliteTable(
  'user',
  {
    id: text('id').primaryKey(),
    email: text('email'),
    emailHash: text('email_hash'),
    phone: text('phone'),
    phoneHash: text('phone_hash'),
    oauth_register_id: text('oauth_register_id').references(() => usersOauthTable.id),
    password: text('password'),
    isEmailVerified: integer('is_email_verified').default(0),
    isPhoneVerified: integer('is_phone_verified').default(0),
    /**
     * - 0 disabled
     * - 1 activate
     */
    status: integer('status').default(0),
    nickname: text('nickname'),
    language: text('language'),
    country: text('country'),
    createdAt: integer('created_at').default(Date.now()),
    updatedAt: integer('updated_at').default(Date.now()),
    isDeleted: integer('is_deleted'),
  },
  (table) => {
    return {
      userUniqueIndex: uniqueIndex('user_unique_index').on(
        table.email,
        table.phone,
        table.oauth_register_id,
      ),
    }
  },
)

export const usersOauthTable = sqliteTable(
  'users_oauth',
  {
    id: text('id').primaryKey(),
    userId: text('user_id').references(() => userTable.id),
    provider: text('provider').notNull(),
    providerUserId: text('provider_user_id').notNull(),
    accessToken: text('access_token'),
    refreshToken: text('refresh_token'),
    expiresAt: integer('expires_at'),
    createdAt: integer('created_at').default(Date.now()),
    updatedAt: integer('updated_at').default(Date.now()),
    isDeleted: integer('is_deleted'),
  },
  (table) => {
    return {
      uniqueProviderUserIndex: uniqueIndex('unique_provider_user_index').on(
        table.provider,
        table.providerUserId,
      ),
    }
  },
)

export const verificationTable = sqliteTable(
  'verification',
  {
    id: text('id').primaryKey(),
    userId: text('user_id')
      .references(() => userTable.id),
    status: integer('status').default(0),
    verifyId: text('verify_id').notNull(),
    expiresAt: integer('expires_at').notNull(),
    createIp: text('create_ip'),
    verifyIp: text('verify_ip'),
    verifyData: text('verify_data'),
    /**
     * serial number
     */
    serial: text('serial'),
    /**
     * - email
     * - phone
     */
    type: text('type'),
    /**
     * Used to validate type records, Write routing address
     *
     * example:
     * - 1 retrieve password
     * - 2 modify email
     * - 3 modify phone
     * - 4 other
     */
    action: text('action'),
    createdAt: integer('created_at').default(Date.now()),
    updatedAt: integer('updated_at').default(Date.now()),
    isDeleted: integer('is_deleted'),
  },
)

export const mfaTable = sqliteTable(
  'mfa',
  {
    id: text('id').primaryKey(),
    userId: text('user_id')
      .notNull()
      .references(() => userTable.id),
    type: text('type').notNull(),
    name: text('name').notNull(),
    status: text('status').notNull(),
    value: text('value'),
    lastVerifiedAt: integer('last_verified_at'),
    createdAt: integer('created_at').default(Date.now()),
    updatedAt: integer('updated_at').default(Date.now()),
    isDeleted: integer('is_deleted'),
  },
)

export const sessionTable = sqliteTable(
  'session',
  {
    id: text('id').primaryKey(),
    userId: text('user_id')
      .notNull()
      .references(() => userTable.id),
    sessionToken: text('session_token').notNull(),
    expiresAt: integer('expires_at').notNull(),
    status: text('status').notNull(),
    mfaId: text('mfa_id')
      .references(() => mfaTable.id),
    // [
    //   {
    //     ip: '',
    //     country: '',
    //     deviceInfo: '',
    //     createdAt: Date.now(),
    //   },
    // ]
    metadata: blob('metadata').notNull(),
    createdAt: integer('created_at').default(Date.now()),
    updatedAt: integer('updated_at').default(Date.now()),
    isDeleted: integer('is_deleted'),
  },
)

export const activityLogTable = sqliteTable(
  'activity_log',
  {
    id: text('id').primaryKey(),
    userId: text('user_id')
      .notNull()
      .references(() => userTable.id),
    action: text('action').notNull(),
    details: text('details'),
    sessionId: text('session_id')
      .notNull()
      .references(() => sessionTable.id),
    createdAt: integer('created_at').default(Date.now()),
    isDeleted: integer('is_deleted'),
  },
)

export const tokens = sqliteTable(
  'tokens',
  {
    id: text('id').primaryKey(),
    userId: text('user_id')
      .notNull()
      .references(() => userTable.id),
    token: text('token').notNull(),
    resources: blob('resources'),
    accesses: blob('accesses'),
    expiresAt: integer('expires_at'),
    createdAt: integer('created_at').default(Date.now()),
    updatedAt: integer('updated_at').default(Date.now()),
    lastUsedAt: integer('last_used_at').default(Date.now()),
    isDeleted: integer('is_deleted'),
  },
)