Permissions: rename Grants, add wildcards, local groups, profile
- Rename Grant -> Permission end-to-end (model, routes, view, frontend, bootstrap) and add an idempotent redis migration for existing records. - utils/roles.js: glob domain matching (* = one label, ** = any depth) against the full host; authz passes the full hostname. - Local groups: LocalGroup model + admin routes/UI; membership merged into Permission.effectiveFor so app groups behave like SSO groups. - Subject autocomplete via GET /api/permission/subjects (users + derived groups). - User profile page (/profile) and username in the navbar; /api/user/me now returns merged/local/external groups. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,21 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Bootstrap a global-admin Grant for a user so there is always someone who can
|
||||
* manage the system after per-domain authorization is enabled.
|
||||
* Bootstrap a global-admin Permission for a user so there is always someone who
|
||||
* can manage the system after per-domain authorization is enabled.
|
||||
*
|
||||
* Usage:
|
||||
* node migrations/grant_bootstrap.js [username]
|
||||
* node migrations/permission_bootstrap.js [username]
|
||||
*
|
||||
* Defaults to the first entry in conf.auth.adminUsers (or 'proxyadmin2').
|
||||
* Note: members of conf.auth.adminUsers / conf.auth.adminGroups are already
|
||||
* treated as admins without a Grant; this just makes it explicit/visible in the
|
||||
* grant list and survives config changes.
|
||||
* treated as admins without a Permission; this just makes it explicit/visible in
|
||||
* the permission list and survives config changes.
|
||||
*/
|
||||
|
||||
const conf = require('@simpleworkjs/conf');
|
||||
require('../models'); // register all models
|
||||
const {Grant} = require('../models/grant');
|
||||
const {Permission} = require('../models/permission');
|
||||
|
||||
(async function(){
|
||||
try{
|
||||
@@ -23,7 +23,7 @@ const {Grant} = require('../models/grant');
|
||||
|| (conf.auth && conf.auth.adminUsers && conf.auth.adminUsers[0])
|
||||
|| 'proxyadmin2';
|
||||
|
||||
let grant = await Grant.create({
|
||||
let permission = await Permission.create({
|
||||
subjectType: 'user',
|
||||
subject: username,
|
||||
scope: 'global',
|
||||
@@ -31,9 +31,9 @@ const {Grant} = require('../models/grant');
|
||||
created_by: username,
|
||||
});
|
||||
|
||||
console.log(`Granted global admin to "${username}":`, grant.id);
|
||||
console.log(`Granted global admin to "${username}":`, permission.id);
|
||||
}catch(error){
|
||||
console.error('grant_bootstrap error', error);
|
||||
console.error('permission_bootstrap error', error);
|
||||
}finally{
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Data migration for the Grant -> Permission rename.
|
||||
*
|
||||
* model-redis namespaces keys by the JS class name, so renaming the class moved
|
||||
* storage from `<prefix>Grant` / `<prefix>Grant_<id>` to `<prefix>Permission*`.
|
||||
* This copies every old Grant record into the Permission model (ids are
|
||||
* unchanged — mkId never encoded the word "grant") and then removes the old
|
||||
* records. Idempotent: safe to re-run (already-migrated ids just upsert; missing
|
||||
* old records are skipped).
|
||||
*
|
||||
* Usage:
|
||||
* node migrations/rename_grant_to_permission.js
|
||||
*/
|
||||
|
||||
const Table = require('../models'); // base Table (shares the app's client/prefix)
|
||||
require('../models'); // register all models (incl. Permission)
|
||||
const {Permission} = require('../models/permission');
|
||||
|
||||
// A throwaway model whose class name is literally "Grant" so it reads the old
|
||||
// namespace regardless of the configured key prefix.
|
||||
class Grant extends Table{
|
||||
static _key = 'id';
|
||||
static _keyMap = Permission._keyMap;
|
||||
}
|
||||
Grant.register();
|
||||
|
||||
(async function(){
|
||||
try{
|
||||
let old = [];
|
||||
try{
|
||||
old = await Grant.listDetail();
|
||||
}catch(error){
|
||||
console.log('No legacy Grant records found; nothing to migrate.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log(`Found ${old.length} Grant record(s) to migrate.`);
|
||||
let migrated = 0;
|
||||
for(let g of old){
|
||||
// Permission.create is an upsert on the deterministic id.
|
||||
await Permission.create({
|
||||
subjectType: g.subjectType,
|
||||
subject: g.subject,
|
||||
scope: g.scope,
|
||||
domain: g.domain,
|
||||
role: g.role,
|
||||
created_by: g.created_by,
|
||||
created_on: g.created_on,
|
||||
});
|
||||
migrated++;
|
||||
}
|
||||
|
||||
// Remove the legacy records now that they live under Permission.
|
||||
for(let g of old){
|
||||
try{
|
||||
let inst = await Grant.get(g.id);
|
||||
await inst.remove();
|
||||
}catch(error){ /* already gone */ }
|
||||
}
|
||||
|
||||
console.log(`Migrated ${migrated} record(s) Grant -> Permission and removed the old entries.`);
|
||||
process.exit(0);
|
||||
}catch(error){
|
||||
console.error('rename_grant_to_permission error', error);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user