mirror of
https://github.com/TiddlyWiki/TiddlyWiki5.git
synced 2026-04-26 13:14:52 +00:00
MWS authentication (#8596)
* mws authentication * add more tests and permission checkers * add logic to ensure that only authenticated users' requests are handled * add custom login page * Implement user authentication as well as session handling * work on user operations authorization * add middleware to route handlers for bags & tiddlers routes * add feature that only returns the tiddlers and bags which the user has permission to access on index page * refactor auth routes & added user management page * fix Ci Test failure issue * fix users list page, add manage roles page * add commands and scripts to create new user & assign roles and permissions * resolved ci-test failure * add ACL permissions to bags & tiddlers on creation * fix comments and access control list bug * fix indentation issues * working on user profile edit * remove list users command & added support for database in server options * implement user profile update and password change feature * update plugin readme * implement command which triggers protected mode on the server * revert server-wide auth flag. Implement selective authorization * ACL management feature * Complete Access control list implementation * Added support to manage users' assigned role by admin * fix comments * fix comment
This commit is contained in:
@@ -25,6 +25,16 @@ function SqlTiddlerDatabase(options) {
|
||||
databasePath: options.databasePath,
|
||||
engine: options.engine
|
||||
});
|
||||
this.entityTypeToTableMap = {
|
||||
bag: {
|
||||
table: "bags",
|
||||
column: "bag_name"
|
||||
},
|
||||
recipe: {
|
||||
table: "recipes",
|
||||
column: "recipe_name"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
SqlTiddlerDatabase.prototype.close = function() {
|
||||
@@ -38,6 +48,83 @@ SqlTiddlerDatabase.prototype.transaction = function(fn) {
|
||||
|
||||
SqlTiddlerDatabase.prototype.createTables = function() {
|
||||
this.engine.runStatements([`
|
||||
-- Users table
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
user_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
created_at TEXT DEFAULT (datetime('now')),
|
||||
last_login TEXT
|
||||
)
|
||||
`,`
|
||||
-- User Session table
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
user_id INTEGER NOT NULL,
|
||||
session_id TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
last_accessed TEXT NOT NULL,
|
||||
PRIMARY KEY (user_id),
|
||||
FOREIGN KEY (user_id) REFERENCES users(user_id)
|
||||
)
|
||||
`,`
|
||||
-- Groups table
|
||||
CREATE TABLE IF NOT EXISTS groups (
|
||||
group_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
group_name TEXT UNIQUE NOT NULL,
|
||||
description TEXT
|
||||
)
|
||||
`,`
|
||||
-- Roles table
|
||||
CREATE TABLE IF NOT EXISTS roles (
|
||||
role_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
role_name TEXT UNIQUE NOT NULL,
|
||||
description TEXT
|
||||
)
|
||||
`,`
|
||||
-- Permissions table
|
||||
CREATE TABLE IF NOT EXISTS permissions (
|
||||
permission_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
permission_name TEXT UNIQUE NOT NULL,
|
||||
description TEXT
|
||||
)
|
||||
`,`
|
||||
-- User-Group association table
|
||||
CREATE TABLE IF NOT EXISTS user_groups (
|
||||
user_id INTEGER,
|
||||
group_id INTEGER,
|
||||
PRIMARY KEY (user_id, group_id),
|
||||
FOREIGN KEY (user_id) REFERENCES users(user_id),
|
||||
FOREIGN KEY (group_id) REFERENCES groups(group_id)
|
||||
)
|
||||
`,`
|
||||
-- User-Role association table
|
||||
CREATE TABLE IF NOT EXISTS user_roles (
|
||||
user_id INTEGER,
|
||||
role_id INTEGER,
|
||||
PRIMARY KEY (user_id, role_id),
|
||||
FOREIGN KEY (user_id) REFERENCES users(user_id),
|
||||
FOREIGN KEY (role_id) REFERENCES roles(role_id)
|
||||
)
|
||||
`,`
|
||||
-- Group-Role association table
|
||||
CREATE TABLE IF NOT EXISTS group_roles (
|
||||
group_id INTEGER,
|
||||
role_id INTEGER,
|
||||
PRIMARY KEY (group_id, role_id),
|
||||
FOREIGN KEY (group_id) REFERENCES groups(group_id),
|
||||
FOREIGN KEY (role_id) REFERENCES roles(role_id)
|
||||
)
|
||||
`,`
|
||||
-- Role-Permission association table
|
||||
CREATE TABLE IF NOT EXISTS role_permissions (
|
||||
role_id INTEGER,
|
||||
permission_id INTEGER,
|
||||
PRIMARY KEY (role_id, permission_id),
|
||||
FOREIGN KEY (role_id) REFERENCES roles(role_id),
|
||||
FOREIGN KEY (permission_id) REFERENCES permissions(permission_id)
|
||||
)
|
||||
`,`
|
||||
-- Bags have names and access control settings
|
||||
CREATE TABLE IF NOT EXISTS bags (
|
||||
bag_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -82,6 +169,26 @@ SqlTiddlerDatabase.prototype.createTables = function() {
|
||||
FOREIGN KEY (tiddler_id) REFERENCES tiddlers(tiddler_id) ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
UNIQUE (tiddler_id, field_name)
|
||||
)
|
||||
`,`
|
||||
-- ACL table (using bag/recipe ids directly)
|
||||
CREATE TABLE IF NOT EXISTS acl (
|
||||
acl_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
entity_name TEXT NOT NULL,
|
||||
entity_type TEXT NOT NULL CHECK (entity_type IN ('bag', 'recipe')),
|
||||
role_id INTEGER,
|
||||
permission_id INTEGER,
|
||||
FOREIGN KEY (role_id) REFERENCES roles(role_id),
|
||||
FOREIGN KEY (permission_id) REFERENCES permissions(permission_id)
|
||||
)
|
||||
`,`
|
||||
-- Indexes for performance (we can add more as needed based on query patterns)
|
||||
CREATE INDEX IF NOT EXISTS idx_tiddlers_bag_id ON tiddlers(bag_id)
|
||||
`,`
|
||||
CREATE INDEX IF NOT EXISTS idx_fields_tiddler_id ON fields(tiddler_id)
|
||||
`,`
|
||||
CREATE INDEX IF NOT EXISTS idx_recipe_bags_recipe_id ON recipe_bags(recipe_id)
|
||||
`,`
|
||||
CREATE INDEX IF NOT EXISTS idx_acl_entity_id ON acl(entity_name)
|
||||
`]);
|
||||
};
|
||||
|
||||
@@ -101,7 +208,7 @@ Returns the bag_id of the bag
|
||||
SqlTiddlerDatabase.prototype.createBag = function(bag_name,description,accesscontrol) {
|
||||
accesscontrol = accesscontrol || "";
|
||||
// Run the queries
|
||||
this.engine.runStatement(`
|
||||
var bag = this.engine.runStatement(`
|
||||
INSERT OR IGNORE INTO bags (bag_name, accesscontrol, description)
|
||||
VALUES ($bag_name, '', '')
|
||||
`,{
|
||||
@@ -117,6 +224,14 @@ SqlTiddlerDatabase.prototype.createBag = function(bag_name,description,accesscon
|
||||
$accesscontrol: accesscontrol,
|
||||
$description: description
|
||||
});
|
||||
|
||||
const admin = this.getRoleByName("ADMIN");
|
||||
if(admin) {
|
||||
const readPermission = this.getPermissionByName("READ");
|
||||
const writePermission = this.getPermissionByName("WRITE");
|
||||
// this.createACL(bag_name, "bag", admin.role_id, readPermission.permission_id);
|
||||
// this.createACL(bag_name, "bag", admin.role_id, writePermission.permission_id);
|
||||
}
|
||||
return updateBags.lastInsertRowid;
|
||||
};
|
||||
|
||||
@@ -180,6 +295,16 @@ SqlTiddlerDatabase.prototype.createRecipe = function(recipe_name,bag_names,descr
|
||||
$recipe_name: recipe_name,
|
||||
$bag_names: JSON.stringify(bag_names)
|
||||
});
|
||||
|
||||
|
||||
// update the permissions on ACL records
|
||||
const admin = this.getRoleByName("ADMIN");
|
||||
if(admin) {
|
||||
const readPermission = this.getPermissionByName("READ");
|
||||
const writePermission = this.getPermissionByName("WRITE");
|
||||
// this.createACL(recipe_name, "recipe", admin.role_id, readPermission.permission_id);
|
||||
// this.createACL(recipe_name, "recipe", admin.role_id, writePermission.permission_id);
|
||||
}
|
||||
return updateRecipes.lastInsertRowid;
|
||||
};
|
||||
|
||||
@@ -374,6 +499,102 @@ SqlTiddlerDatabase.prototype.getRecipeTiddler = function(title,recipe_name) {
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
Checks if a user has permission to access a recipe
|
||||
*/
|
||||
SqlTiddlerDatabase.prototype.hasRecipePermission = function(userId, recipeName, permissionName) {
|
||||
return this.checkACLPermission(userId, "recipe", recipeName, permissionName)
|
||||
};
|
||||
|
||||
/*
|
||||
Checks if a user has permission to access a bag
|
||||
*/
|
||||
SqlTiddlerDatabase.prototype.hasBagPermission = function(userId, bagName, permissionName) {
|
||||
return this.checkACLPermission(userId, "bag", bagName, permissionName)
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.getACLByName = function(entityType, entityName, fetchAll) {
|
||||
const entityInfo = this.entityTypeToTableMap[entityType];
|
||||
if (!entityInfo) {
|
||||
throw new Error("Invalid entity type: " + entityType);
|
||||
}
|
||||
|
||||
// First, check if there's an ACL record for the entity and get the permission_id
|
||||
var checkACLExistsQuery = `
|
||||
SELECT *
|
||||
FROM acl
|
||||
WHERE entity_type = $entity_type
|
||||
AND entity_name = $entity_name
|
||||
`;
|
||||
|
||||
if (!fetchAll) {
|
||||
checkACLExistsQuery += ' LIMIT 1'
|
||||
}
|
||||
|
||||
const aclRecord = this.engine[fetchAll ? 'runStatementGetAll' : 'runStatementGet'](checkACLExistsQuery, {
|
||||
$entity_type: entityType,
|
||||
$entity_name: entityName
|
||||
});
|
||||
|
||||
return aclRecord;
|
||||
}
|
||||
|
||||
SqlTiddlerDatabase.prototype.checkACLPermission = function(userId, entityType, entityName) {
|
||||
// if the entityName starts with "$:/", we'll assume its a system bag/recipe, then grant the user permission
|
||||
if(entityName.startsWith("$:/")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const aclRecord = this.getACLByName(entityType, entityName);
|
||||
|
||||
// If no ACL record exists, return true for hasPermission
|
||||
if (!aclRecord) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If ACL record exists, check for user permission using the retrieved permission_id
|
||||
const checkPermissionQuery = `
|
||||
SELECT 1
|
||||
FROM users u
|
||||
JOIN user_roles ur ON u.user_id = ur.user_id
|
||||
JOIN roles r ON ur.role_id = r.role_id
|
||||
JOIN acl a ON r.role_id = a.role_id
|
||||
WHERE u.user_id = $user_id
|
||||
AND a.entity_type = $entity_type
|
||||
AND a.entity_name = $entity_name
|
||||
AND a.permission_id = $permission_id
|
||||
LIMIT 1
|
||||
`;
|
||||
|
||||
const result = this.engine.runStatementGet(checkPermissionQuery, {
|
||||
$user_id: userId,
|
||||
$entity_type: entityType,
|
||||
$entity_name: entityName,
|
||||
$permission_id: aclRecord.permission_id
|
||||
});
|
||||
|
||||
const hasPermission = result !== undefined;
|
||||
|
||||
return hasPermission;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the ACL records for an entity (bag or recipe)
|
||||
*/
|
||||
SqlTiddlerDatabase.prototype.getEntityAclRecords = function(entityName) {
|
||||
const checkACLExistsQuery = `
|
||||
SELECT *
|
||||
FROM acl
|
||||
WHERE entity_name = $entity_name
|
||||
`;
|
||||
|
||||
const aclRecords = this.engine.runStatementGetAll(checkACLExistsQuery, {
|
||||
$entity_name: entityName
|
||||
});
|
||||
|
||||
return aclRecords
|
||||
}
|
||||
|
||||
/*
|
||||
Get the titles of the tiddlers in a bag. Returns an empty array for bags that do not exist
|
||||
*/
|
||||
@@ -575,6 +796,576 @@ SqlTiddlerDatabase.prototype.getRecipeTiddlerAttachmentBlob = function(title,rec
|
||||
return row ? row.attachment_blob : null;
|
||||
};
|
||||
|
||||
// User CRUD operations
|
||||
SqlTiddlerDatabase.prototype.createUser = function(username, email, password) {
|
||||
const result = this.engine.runStatement(`
|
||||
INSERT INTO users (username, email, password)
|
||||
VALUES ($username, $email, $password)
|
||||
`, {
|
||||
$username: username,
|
||||
$email: email,
|
||||
$password: password
|
||||
});
|
||||
return result.lastInsertRowid;
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.getUser = function(userId) {
|
||||
return this.engine.runStatementGet(`
|
||||
SELECT * FROM users WHERE user_id = $userId
|
||||
`, {
|
||||
$userId: userId
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.getUserByUsername = function(username) {
|
||||
return this.engine.runStatementGet(`
|
||||
SELECT * FROM users WHERE username = $username
|
||||
`, {
|
||||
$username: username
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.updateUser = function (userId, username, email, roleId) {
|
||||
const existingUser = this.engine.runStatement(`
|
||||
SELECT user_id FROM users
|
||||
WHERE email = $email AND user_id != $userId
|
||||
`, {
|
||||
$email: email,
|
||||
$userId: userId
|
||||
});
|
||||
|
||||
if (existingUser.length > 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Email address already in use by another user."
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
this.engine.transaction(() => {
|
||||
// Update user information
|
||||
this.engine.runStatement(`
|
||||
UPDATE users
|
||||
SET username = $username, email = $email
|
||||
WHERE user_id = $userId
|
||||
`, {
|
||||
$userId: userId,
|
||||
$username: username,
|
||||
$email: email
|
||||
});
|
||||
|
||||
if (roleId) {
|
||||
// Remove all existing roles for the user
|
||||
this.engine.runStatement(`
|
||||
DELETE FROM user_roles
|
||||
WHERE user_id = $userId
|
||||
`, {
|
||||
$userId: userId
|
||||
});
|
||||
|
||||
// Add the new role
|
||||
this.engine.runStatement(`
|
||||
INSERT INTO user_roles (user_id, role_id)
|
||||
VALUES ($userId, $roleId)
|
||||
`, {
|
||||
$userId: userId,
|
||||
$roleId: roleId
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "User profile and role updated successfully."
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Failed to update user profile: " + error.message
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.updateUserPassword = function (userId, newHash) {
|
||||
try {
|
||||
this.engine.runStatement(`
|
||||
UPDATE users
|
||||
SET password = $newHash
|
||||
WHERE user_id = $userId
|
||||
`, {
|
||||
$userId: userId,
|
||||
$newHash: newHash,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Password updated successfully."
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Failed to update password: " + error.message
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.deleteUser = function(userId) {
|
||||
this.engine.runStatement(`
|
||||
DELETE FROM users WHERE user_id = $userId
|
||||
`, {
|
||||
$userId: userId
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.listUsers = function() {
|
||||
return this.engine.runStatementGetAll(`
|
||||
SELECT * FROM users ORDER BY username
|
||||
`);
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.createOrUpdateUserSession = function(userId, sessionId) {
|
||||
const currentTimestamp = new Date().toISOString();
|
||||
|
||||
// First, try to update an existing session
|
||||
const updateResult = this.engine.runStatement(`
|
||||
UPDATE sessions
|
||||
SET session_id = $sessionId, last_accessed = $timestamp
|
||||
WHERE user_id = $userId
|
||||
`, {
|
||||
$userId: userId,
|
||||
$sessionId: sessionId,
|
||||
$timestamp: currentTimestamp
|
||||
});
|
||||
|
||||
// If no existing session was updated, create a new one
|
||||
if (updateResult.changes === 0) {
|
||||
this.engine.runStatement(`
|
||||
INSERT INTO sessions (user_id, session_id, created_at, last_accessed)
|
||||
VALUES ($userId, $sessionId, $timestamp, $timestamp)
|
||||
`, {
|
||||
$userId: userId,
|
||||
$sessionId: sessionId,
|
||||
$timestamp: currentTimestamp
|
||||
});
|
||||
}
|
||||
|
||||
return sessionId;
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.findUserBySessionId = function(sessionId) {
|
||||
// First, get the user_id from the sessions table
|
||||
const sessionResult = this.engine.runStatementGet(`
|
||||
SELECT user_id, last_accessed
|
||||
FROM sessions
|
||||
WHERE session_id = $sessionId
|
||||
`, {
|
||||
$sessionId: sessionId
|
||||
});
|
||||
|
||||
if (!sessionResult) {
|
||||
return null; // Session not found
|
||||
}
|
||||
|
||||
const lastAccessed = new Date(sessionResult.last_accessed);
|
||||
const expirationTime = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
|
||||
if (new Date() - lastAccessed > expirationTime) {
|
||||
// Session has expired
|
||||
this.deleteSession(sessionId);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Update the last_accessed timestamp
|
||||
const currentTimestamp = new Date().toISOString();
|
||||
this.engine.runStatement(`
|
||||
UPDATE sessions
|
||||
SET last_accessed = $timestamp
|
||||
WHERE session_id = $sessionId
|
||||
`, {
|
||||
$sessionId: sessionId,
|
||||
$timestamp: currentTimestamp
|
||||
});
|
||||
|
||||
const userResult = this.engine.runStatementGet(`
|
||||
SELECT *
|
||||
FROM users
|
||||
WHERE user_id = $userId
|
||||
`, {
|
||||
$userId: sessionResult.user_id
|
||||
});
|
||||
|
||||
if (!userResult) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return userResult;
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.deleteSession = function(sessionId) {
|
||||
this.engine.runStatement(`
|
||||
DELETE FROM sessions
|
||||
WHERE session_id = $sessionId
|
||||
`, {
|
||||
$sessionId: sessionId
|
||||
});
|
||||
};
|
||||
|
||||
// Group CRUD operations
|
||||
SqlTiddlerDatabase.prototype.createGroup = function(groupName, description) {
|
||||
const result = this.engine.runStatement(`
|
||||
INSERT INTO groups (group_name, description)
|
||||
VALUES ($groupName, $description)
|
||||
`, {
|
||||
$groupName: groupName,
|
||||
$description: description
|
||||
});
|
||||
return result.lastInsertRowid;
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.getGroup = function(groupId) {
|
||||
return this.engine.runStatementGet(`
|
||||
SELECT * FROM groups WHERE group_id = $groupId
|
||||
`, {
|
||||
$groupId: groupId
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.updateGroup = function(groupId, groupName, description) {
|
||||
this.engine.runStatement(`
|
||||
UPDATE groups
|
||||
SET group_name = $groupName, description = $description
|
||||
WHERE group_id = $groupId
|
||||
`, {
|
||||
$groupId: groupId,
|
||||
$groupName: groupName,
|
||||
$description: description
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.deleteGroup = function(groupId) {
|
||||
this.engine.runStatement(`
|
||||
DELETE FROM groups WHERE group_id = $groupId
|
||||
`, {
|
||||
$groupId: groupId
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.listGroups = function() {
|
||||
return this.engine.runStatementGetAll(`
|
||||
SELECT * FROM groups ORDER BY group_name
|
||||
`);
|
||||
};
|
||||
|
||||
// Role CRUD operations
|
||||
SqlTiddlerDatabase.prototype.createRole = function(roleName, description) {
|
||||
const result = this.engine.runStatement(`
|
||||
INSERT OR IGNORE INTO roles (role_name, description)
|
||||
VALUES ($roleName, $description)
|
||||
`, {
|
||||
$roleName: roleName,
|
||||
$description: description
|
||||
});
|
||||
return result.lastInsertRowid;
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.getRole = function(roleId) {
|
||||
return this.engine.runStatementGet(`
|
||||
SELECT * FROM roles WHERE role_id = $roleId
|
||||
`, {
|
||||
$roleId: roleId
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.getRoleByName = function(roleName) {
|
||||
return this.engine.runStatementGet(`
|
||||
SELECT * FROM roles WHERE role_name = $roleName
|
||||
`, {
|
||||
$roleName: roleName
|
||||
});
|
||||
}
|
||||
|
||||
SqlTiddlerDatabase.prototype.updateRole = function(roleId, roleName, description) {
|
||||
this.engine.runStatement(`
|
||||
UPDATE roles
|
||||
SET role_name = $roleName, description = $description
|
||||
WHERE role_id = $roleId
|
||||
`, {
|
||||
$roleId: roleId,
|
||||
$roleName: roleName,
|
||||
$description: description
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.deleteRole = function(roleId) {
|
||||
this.engine.runStatement(`
|
||||
DELETE FROM roles WHERE role_id = $roleId
|
||||
`, {
|
||||
$roleId: roleId
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.listRoles = function() {
|
||||
return this.engine.runStatementGetAll(`
|
||||
SELECT * FROM roles ORDER BY role_name
|
||||
`);
|
||||
};
|
||||
|
||||
// Permission CRUD operations
|
||||
SqlTiddlerDatabase.prototype.createPermission = function(permissionName, description) {
|
||||
const result = this.engine.runStatement(`
|
||||
INSERT OR IGNORE INTO permissions (permission_name, description)
|
||||
VALUES ($permissionName, $description)
|
||||
`, {
|
||||
$permissionName: permissionName,
|
||||
$description: description
|
||||
});
|
||||
return result.lastInsertRowid;
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.getPermission = function(permissionId) {
|
||||
return this.engine.runStatementGet(`
|
||||
SELECT * FROM permissions WHERE permission_id = $permissionId
|
||||
`, {
|
||||
$permissionId: permissionId
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.getPermissionByName = function(permissionName) {
|
||||
return this.engine.runStatementGet(`
|
||||
SELECT * FROM permissions WHERE permission_name = $permissionName
|
||||
`, {
|
||||
$permissionName: permissionName
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.updatePermission = function(permissionId, permissionName, description) {
|
||||
this.engine.runStatement(`
|
||||
UPDATE permissions
|
||||
SET permission_name = $permissionName, description = $description
|
||||
WHERE permission_id = $permissionId
|
||||
`, {
|
||||
$permissionId: permissionId,
|
||||
$permissionName: permissionName,
|
||||
$description: description
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.deletePermission = function(permissionId) {
|
||||
this.engine.runStatement(`
|
||||
DELETE FROM permissions WHERE permission_id = $permissionId
|
||||
`, {
|
||||
$permissionId: permissionId
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.listPermissions = function() {
|
||||
return this.engine.runStatementGetAll(`
|
||||
SELECT * FROM permissions ORDER BY permission_name
|
||||
`);
|
||||
};
|
||||
|
||||
// ACL CRUD operations
|
||||
SqlTiddlerDatabase.prototype.createACL = function(entityName, entityType, roleId, permissionId) {
|
||||
if(!entityName.startsWith("$:/")) {
|
||||
const result = this.engine.runStatement(`
|
||||
INSERT OR IGNORE INTO acl (entity_name, entity_type, role_id, permission_id)
|
||||
VALUES ($entityName, $entityType, $roleId, $permissionId)
|
||||
`,
|
||||
{
|
||||
$entityName: entityName,
|
||||
$entityType: entityType,
|
||||
$roleId: roleId,
|
||||
$permissionId: permissionId
|
||||
});
|
||||
return result.lastInsertRowid;
|
||||
}
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.getACL = function(aclId) {
|
||||
return this.engine.runStatementGet(`
|
||||
SELECT * FROM acl WHERE acl_id = $aclId
|
||||
`, {
|
||||
$aclId: aclId
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.updateACL = function(aclId, entityId, entityType, roleId, permissionId) {
|
||||
this.engine.runStatement(`
|
||||
UPDATE acl
|
||||
SET entity_name = $entityId, entity_type = $entityType,
|
||||
role_id = $roleId, permission_id = $permissionId
|
||||
WHERE acl_id = $aclId
|
||||
`, {
|
||||
$aclId: aclId,
|
||||
$entityId: entityId,
|
||||
$entityType: entityType,
|
||||
$roleId: roleId,
|
||||
$permissionId: permissionId
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.deleteACL = function(aclId) {
|
||||
this.engine.runStatement(`
|
||||
DELETE FROM acl WHERE acl_id = $aclId
|
||||
`, {
|
||||
$aclId: aclId
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.listACLs = function() {
|
||||
return this.engine.runStatementGetAll(`
|
||||
SELECT * FROM acl ORDER BY entity_type, entity_name
|
||||
`);
|
||||
};
|
||||
|
||||
// Association management functions
|
||||
SqlTiddlerDatabase.prototype.addUserToGroup = function(userId, groupId) {
|
||||
this.engine.runStatement(`
|
||||
INSERT OR IGNORE INTO user_groups (user_id, group_id)
|
||||
VALUES ($userId, $groupId)
|
||||
`, {
|
||||
$userId: userId,
|
||||
$groupId: groupId
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.isUserInGroup = function(userId, groupId) {
|
||||
const result = this.engine.runStatementGet(`
|
||||
SELECT 1 FROM user_groups
|
||||
WHERE user_id = $userId AND group_id = $groupId
|
||||
`, {
|
||||
$userId: userId,
|
||||
$groupId: groupId
|
||||
});
|
||||
return result !== undefined;
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.removeUserFromGroup = function(userId, groupId) {
|
||||
this.engine.runStatement(`
|
||||
DELETE FROM user_groups
|
||||
WHERE user_id = $userId AND group_id = $groupId
|
||||
`, {
|
||||
$userId: userId,
|
||||
$groupId: groupId
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.addRoleToUser = function(userId, roleId) {
|
||||
this.engine.runStatement(`
|
||||
INSERT OR IGNORE INTO user_roles (user_id, role_id)
|
||||
VALUES ($userId, $roleId)
|
||||
`, {
|
||||
$userId: userId,
|
||||
$roleId: roleId
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.removeRoleFromUser = function(userId, roleId) {
|
||||
this.engine.runStatement(`
|
||||
DELETE FROM user_roles
|
||||
WHERE user_id = $userId AND role_id = $roleId
|
||||
`, {
|
||||
$userId: userId,
|
||||
$roleId: roleId
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.addRoleToGroup = function(groupId, roleId) {
|
||||
this.engine.runStatement(`
|
||||
INSERT OR IGNORE INTO group_roles (group_id, role_id)
|
||||
VALUES ($groupId, $roleId)
|
||||
`, {
|
||||
$groupId: groupId,
|
||||
$roleId: roleId
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.removeRoleFromGroup = function(groupId, roleId) {
|
||||
this.engine.runStatement(`
|
||||
DELETE FROM group_roles
|
||||
WHERE group_id = $groupId AND role_id = $roleId
|
||||
`, {
|
||||
$groupId: groupId,
|
||||
$roleId: roleId
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.addPermissionToRole = function(roleId, permissionId) {
|
||||
this.engine.runStatement(`
|
||||
INSERT OR IGNORE INTO role_permissions (role_id, permission_id)
|
||||
VALUES ($roleId, $permissionId)
|
||||
`, {
|
||||
$roleId: roleId,
|
||||
$permissionId: permissionId
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.removePermissionFromRole = function(roleId, permissionId) {
|
||||
this.engine.runStatement(`
|
||||
DELETE FROM role_permissions
|
||||
WHERE role_id = $roleId AND permission_id = $permissionId
|
||||
`, {
|
||||
$roleId: roleId,
|
||||
$permissionId: permissionId
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.getUserRoles = function(userId) {
|
||||
const query = `
|
||||
SELECT r.role_id, r.role_name
|
||||
FROM user_roles ur
|
||||
JOIN roles r ON ur.role_id = r.role_id
|
||||
WHERE ur.user_id = $userId
|
||||
LIMIT 1
|
||||
`;
|
||||
|
||||
return this.engine.runStatementGet(query, { $userId: userId });
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.isRoleInUse = function(roleId) {
|
||||
// Check if the role is assigned to any users
|
||||
const userRoleCheck = this.engine.runStatementGet(`
|
||||
SELECT 1
|
||||
FROM user_roles
|
||||
WHERE role_id = $roleId
|
||||
LIMIT 1
|
||||
`, {
|
||||
$roleId: roleId
|
||||
});
|
||||
|
||||
if(userRoleCheck) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if the role is used in any ACLs
|
||||
const aclRoleCheck = this.engine.runStatementGet(`
|
||||
SELECT 1
|
||||
FROM acl
|
||||
WHERE role_id = $roleId
|
||||
LIMIT 1
|
||||
`, {
|
||||
$roleId: roleId
|
||||
});
|
||||
|
||||
if(aclRoleCheck) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we've reached this point, the role is not in use
|
||||
return false;
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.getRoleById = function(roleId) {
|
||||
const role = this.engine.runStatementGet(`
|
||||
SELECT role_id, role_name, description
|
||||
FROM roles
|
||||
WHERE role_id = $roleId
|
||||
`, {
|
||||
$roleId: roleId
|
||||
});
|
||||
|
||||
return role;
|
||||
};
|
||||
|
||||
exports.SqlTiddlerDatabase = SqlTiddlerDatabase;
|
||||
|
||||
})();
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user