feat: implement identity and security features
- Added account types and privileged account types constants. - Created admin user controller for updating user security fields. - Developed role assignment controller for managing user roles. - Implemented validation middleware for request schemas. - Defined user role and auth session models for database interactions. - Created services for authentication, email notifications, and OTP handling. - Developed OAuth service for Google and Apple authentication. - Added JWT utility functions for token generation and verification. - Implemented comprehensive tests for authentication, session management, and password policies. - Created migration for updating user schema and adding new tables for auth sessions and user identities.
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
const userColumns = await queryInterface.describeTable("users");
|
||||
await queryInterface.changeColumn("users", "accountType", {
|
||||
type: Sequelize.ENUM("superadmin", "admin", "manager", "business_customer", "rider", "customer", "support_agent"),
|
||||
allowNull: false, defaultValue: "customer",
|
||||
});
|
||||
await queryInterface.changeColumn("users", "password", { type: Sequelize.STRING, allowNull: true });
|
||||
if (!userColumns.passwordChangedAt) await queryInterface.addColumn("users", "passwordChangedAt", { type: Sequelize.DATE, allowNull: true });
|
||||
await queryInterface.addColumn("users", "tokenVersion", { type: Sequelize.INTEGER, allowNull: false, defaultValue: 0 });
|
||||
await queryInterface.addColumn("users", "lastLoginAt", { type: Sequelize.DATE, allowNull: true });
|
||||
|
||||
await queryInterface.createTable("auth_sessions", {
|
||||
id: { type: Sequelize.UUID, primaryKey: true }, user_id: { type: Sequelize.STRING, allowNull: false, references: { model: "users", key: "id" }, onDelete: "CASCADE" },
|
||||
token_family_id: { type: Sequelize.UUID, allowNull: false }, refresh_token_hash: { type: Sequelize.STRING(64), allowNull: false },
|
||||
device_name: Sequelize.STRING, user_agent: Sequelize.STRING(500), ip_address: Sequelize.STRING(64),
|
||||
remember_me: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: false }, token_version: { type: Sequelize.INTEGER, allowNull: false },
|
||||
last_used_at: Sequelize.DATE, expires_at: { type: Sequelize.DATE, allowNull: false }, revoked_at: Sequelize.DATE,
|
||||
revoked_reason: Sequelize.STRING, replaced_by_session_id: Sequelize.UUID,
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false }, updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
await queryInterface.addIndex("auth_sessions", ["user_id"]);
|
||||
await queryInterface.addIndex("auth_sessions", ["token_family_id"]);
|
||||
await queryInterface.addIndex("auth_sessions", ["expires_at"]);
|
||||
|
||||
await queryInterface.createTable("user_identities", {
|
||||
id: { type: Sequelize.UUID, primaryKey: true }, user_id: { type: Sequelize.STRING, allowNull: false, references: { model: "users", key: "id" }, onDelete: "CASCADE" },
|
||||
provider: { type: Sequelize.ENUM("google", "apple"), allowNull: false }, provider_subject: { type: Sequelize.STRING, allowNull: false },
|
||||
provider_email: Sequelize.STRING, createdAt: { type: Sequelize.DATE, allowNull: false }, updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
await queryInterface.addIndex("user_identities", ["provider", "provider_subject"], { unique: true, name: "user_identity_provider_subject_unique" });
|
||||
await queryInterface.addIndex("user_identities", ["user_id"]);
|
||||
|
||||
await queryInterface.createTable("user_roles", {
|
||||
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
|
||||
user_id: { type: Sequelize.STRING, allowNull: false, references: { model: "users", key: "id" }, onDelete: "CASCADE" },
|
||||
role_id: { type: Sequelize.STRING, allowNull: false, references: { model: "roles", key: "role_id" }, onDelete: "CASCADE" },
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false }, updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
});
|
||||
await queryInterface.addIndex("user_roles", ["user_id", "role_id"], { unique: true, name: "user_role_unique" });
|
||||
await queryInterface.addIndex("rolePermission", ["role_id", "permission_id"], { unique: true, name: "role_permission_unique" });
|
||||
await queryInterface.addIndex("userPermission", ["user_id", "permission_id"], { unique: true, name: "user_permission_unique" });
|
||||
await queryInterface.addIndex("roles", ["roleName"], { unique: true, name: "role_name_unique" });
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.removeIndex("roles", "role_name_unique");
|
||||
await queryInterface.removeIndex("userPermission", "user_permission_unique");
|
||||
await queryInterface.removeIndex("rolePermission", "role_permission_unique");
|
||||
await queryInterface.dropTable("user_roles");
|
||||
await queryInterface.dropTable("user_identities");
|
||||
await queryInterface.dropTable("auth_sessions");
|
||||
await queryInterface.removeColumn("users", "lastLoginAt");
|
||||
await queryInterface.removeColumn("users", "tokenVersion");
|
||||
// passwordChangedAt may predate this migration, so down intentionally preserves it.
|
||||
await queryInterface.changeColumn("users", "password", { type: Sequelize.STRING, allowNull: false });
|
||||
await queryInterface.changeColumn("users", "accountType", { type: Sequelize.ENUM("business_customer", "customer"), defaultValue: "customer" });
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user