AS3W (Access String: Who, What, When) is a lightweight authorization protocol for associating access rules directly with data stored in a database.
Each data records in the database can contain an Access String describing:
- who can access (user IDs and/or group IDs),
- what actions are allowed,
- when the access expires.
Authorization is performed by:
- Generating a normalized Access Request String based on the requesting user.
- Evaluating the Access Request Sting against the stored Access String using regex pattern-matching.
Access String
[RULENAME]\[USERS:@<ID>,@<ID>]|[GROUPS:@<ID>,@<ID>]\[ACTION:@<ACTION>,@<ACTION>]\[UNTIL:<TIMESTAMP>]\[COMMENT]
Fields
| Field | Description |
|---|---|
| USERS | List of users allowed (e.g. users:@id1,@id2) |
| GROUPS | List of groups allowed |
| ACTION | Allowed actions (e.g. data:read, data:write, permission:delete) |
| UNTIL | Expiration timestamp (epoch ms or s). Optional. |
| [RULENAME] | Optional rule name |
| [COMMENT] | Optional rule comment |
Example
MyRule1\users:@e8f58e5f85e8f58\action:@read\until:176427694
users:@87844545445\groups:@devops\action:@read\until:176899568\just another rule
groups:@devops\action:@read\until:1764271971
Access Request String
The Access Request String is a compact expression of:
- user’s ID(s),
- user’s groups,
- requested action,
- current timestamp.
Example Access Request:
users:@e8f58e5f85e8f58|groups:@devops,admin\action:@read\until:176427694
Authorization Logic (Regex)
To determine access:
AS3W_VERIFY( access_string , access_request_string )
AS3W_VERIFY will return true if the access_request_string match the access_string, else it will return false
function esc(str) {
return String(str)
.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&")
.trim();
}
async function _time_regex_filter(ts) {
const digits = String(ts).split('').map(Number);
let regex = '';
for (let i = 0; i < digits.length; i++) {
const n = digits[i];
const L = digits.length - i - 1;
regex += n < 9
? `([${n+1}-9]\\d{${L}}|${n}`
: `(${n}`;
}
return regex + ")))))))))";
}
async function AS3W_ACCESS_STRING(users=[], groups=[], actions=[], time=null) {
//TODO check that # is not par of users, groyps or actions.
users = users.join(",#");
groups = groups.join(",#");
actions = actions.join(",#");
if(users.includes("\\")){
throw new Error("users include a forbidden character");
}
if(groups.includes("\\")){
throw new Error("groups include a forbidden character");
}
if(actions.includes("\\")){
throw new Error("actions include a forbidden character");
}
users = users ? `users:#${users}` : "";
groups = groups ? `groups:#${groups}` : "";
actions = actions ? `action:#${actions}` : "";
time = time ? `until:${esc(time)}` : "";
return [users, groups, actions, time]
.filter(x => x !== "")
.join("\\");
}
/**
* Build the REGEX used to CHECK ACCESS
*/
async function AS3W_ACCESS_REQUEST(users = [], groups = [], actions = [], time = null) {
// Sanitize arrays
const escUsers = users.map(u => esc(u));
const escGroups = groups.map(g => esc(g));
const escActions = actions.map(a => esc(a));
// Build alternation groups: (u1|u2|u3)
const userAlternatives = escUsers.join("|") || "\\*";
const groupAlternatives = escGroups.join("|") || "\\*";
const actionAlternatives = escActions.join("|") || "\\*";
let timeRegex = "";
if (time) {
timeRegex = await _time_regex_filter(time);
}
const regex =
"^" +
"([^\\\\]*\\\\)*" + // allow rule name + prefixes
"(" +
"users:.*#(" + userAlternatives + "|\\*)(,.*|\\b)" +
"(\\\\groups:.*)*|(users:.*\\\\)*" +
"groups:.*#(" + groupAlternatives + "|\\*)(,.*|\\b)" +
")" +
"\\\\action:.*#(" + actionAlternatives + "|\\*)(,.*|\\b)" +
(time
? "\\\\until:(" + timeRegex + ")"
: "(\\\\until:(" + timeRegex + "))?") +
"\\s*$";
return regex;
}
async function AS3W_VERIFY(accessString, requestString) {
// 1. Normalize stored Access String (remove spaces, trim)
const normalizedAS = String(accessString).trim().replace(/\s+/g, "");
// 3. Build regex object
let requestRegex;
try {
requestRegex = new RegExp(requestString);
} catch (err) {
console.error("Invalid AS3W regex:", requestRegexString);
return false;
}
// 4. Perform match
return requestRegex.test(normalizedAS);
}
(async () => {
//const stored = "users:@e8f58e5f85e8f58\\action:@read\\until:176427694";
const access_string = await AS3W_ACCESS_STRING(
["[email protected]","[email protected]"],
["admin", "devops"],
["read","delete"],
176427694
)
console.log(access_string);
const access_request = await AS3W_ACCESS_REQUEST(
["[email protected]"],
["user"],
["write"],
176427694
)
console.log(access_request);
const ok = await AS3W_VERIFY(
access_string,
access_request
);
console.log("ACCESS GRANTED:", ok);
})();
