Skip to content

Commit dc01edd

Browse files
committed
role based access control
1 parent ac38b6f commit dc01edd

6 files changed

Lines changed: 109 additions & 41 deletions

File tree

astro-app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "xnode-auth-frontend",
33
"type": "module",
4-
"version": "1.2.0",
4+
"version": "2.0.0",
55
"scripts": {
66
"dev": "astro dev",
77
"build": "astro build",

astro-app/src/lib/access.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,20 @@ export async function hasAccess({
1515
const memoryUser = users.find((user) =>
1616
Object.keys(memory[domain]).some(
1717
(userReg) =>
18+
// user matches this userReg
1819
(userReg.startsWith("regex:")
1920
? new RegExp(userReg.replace("regex:", "")).test(user)
2021
: userReg === user) &&
21-
new RegExp(memory[domain][userReg].paths).test(path)
22-
)
22+
// role of user exists
23+
Object.hasOwn(
24+
memory[domain].roles,
25+
memory[domain].users[userReg].role,
26+
) &&
27+
// role has access to path
28+
new RegExp(
29+
memory[domain].roles[memory[domain].users[userReg].role].paths,
30+
).test(path),
31+
),
2332
);
2433

2534
if (memoryUser !== undefined) {
@@ -40,27 +49,37 @@ export async function hasAccess({
4049
}[];
4150

4251
for (const externalSource of externalSources.filter((externalSource) =>
43-
new RegExp(externalSource.restrictions.domains).test(domain)
52+
new RegExp(externalSource.restrictions.domains).test(domain),
4453
)) {
4554
const source = await getSource({ id: `external:${externalSource.source}` });
4655
if (source[domain] !== undefined) {
4756
const externalUser = users.find((user) =>
4857
Object.keys(source[domain]).some(
4958
(userReg) =>
59+
// user matches this userReg
5060
(userReg.startsWith("regex:")
5161
? new RegExp(userReg.replace("regex:", "")).test(user)
5262
: userReg === user) &&
53-
new RegExp(source[domain][userReg].paths).test(path) &&
63+
// role of user exists
64+
Object.hasOwn(
65+
memory[domain].roles,
66+
memory[domain].users[userReg].role,
67+
) &&
68+
// role has access to path
69+
new RegExp(
70+
source[domain].roles[source[domain].users[userReg].role].paths,
71+
).test(path) &&
72+
// there is no restriction applied to this domain that rejects this user or path
5473
!externalSource.restrictions.domainSpecific
5574
.filter((restriction) =>
56-
new RegExp(restriction.domains).test(domain)
75+
new RegExp(restriction.domains).test(domain),
5776
)
5877
.some(
5978
(restriction) =>
6079
!new RegExp(restriction.users).test(user) ||
61-
!new RegExp(restriction.paths).test(path)
62-
)
63-
)
80+
!new RegExp(restriction.paths).test(path),
81+
),
82+
),
6483
);
6584

6685
if (externalUser !== undefined) {

astro-app/src/lib/source.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { readFile } from "fs/promises";
22

33
export interface Data {
4-
[domain: string]: { [user: string]: { paths: string } };
4+
[domain: string]: {
5+
users: { [user: string]: { role: string } };
6+
roles: { [role: string]: { paths: string } };
7+
};
58
}
69
const cache = {} as {
710
[id: string]: { data: Data; cachedAt: Date } | undefined;

example/flake.nix

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,22 @@
3535
{ lib, ... }:
3636
{
3737
# START USER CONFIG
38-
services.xnode-auth.domains."xnode-auth-demo".accessList."regex:^eth:.*$" = {
39-
paths = "^\/private(?:\\?.*)?$";
38+
services.xnode-auth.domains."xnode-auth-demo" = {
39+
paths = [
40+
"/private"
41+
"/admin"
42+
];
43+
accessList = {
44+
roles = {
45+
"user".paths = "^\/private(?:\\?.*)?$";
46+
"admin" = { };
47+
};
48+
users = {
49+
"regex:^eth:.*$".role = "user";
50+
"eth:519ce4c129a981b2cbb4c3990b1391da24e8ebf3".role = "admin";
51+
};
52+
};
4053
};
41-
services.xnode-auth.domains."xnode-auth-demo".accessList."eth:519ce4c129a981b2cbb4c3990b1391da24e8ebf3" =
42-
{ };
43-
services.xnode-auth.domains."xnode-auth-demo".paths = [
44-
"/private"
45-
"/admin"
46-
];
4754
services.xnode-auth.externalSources = [
4855
{
4956
# echo -n '{ }' | sudo tee /xnode-auth.json && chown xnode-auth /xnode-auth.json

nix/nixos-module.nix

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,31 +35,61 @@ in
3535
type = lib.types.attrsOf (
3636
lib.types.submodule {
3737
options = {
38-
accessList = lib.mkOption {
39-
type = lib.types.attrsOf (
40-
lib.types.submodule {
41-
options = {
42-
paths = lib.mkOption {
43-
type = lib.types.str;
44-
default = ".*";
45-
example = "^(?:\/admin|\/secret)(?:\?.*)?$";
46-
description = ''
47-
Regex of paths to protect with the accessList authentication.
48-
'';
38+
accessList = {
39+
users = lib.mkOption {
40+
type = lib.types.attrsOf (
41+
lib.types.submodule {
42+
options = {
43+
role = lib.mkOption {
44+
type = lib.types.str;
45+
example = "admin";
46+
description = ''
47+
Role on this domain to grant to this user.
48+
'';
49+
};
4950
};
51+
}
52+
);
53+
default = { };
54+
example = {
55+
"regex:^eth:*.$" = {
56+
role = "user";
57+
};
58+
"eth:519ce4c129a981b2cbb4c3990b1391da24e8ebf3" = {
59+
role = "admin";
5060
};
51-
}
52-
);
53-
default = { };
54-
example = {
55-
"regex:^eth:*.$" = {
56-
paths = "^\/user\/profile(?:\?.*)?$";
5761
};
58-
"eth:519ce4c129a981b2cbb4c3990b1391da24e8ebf3" = { };
62+
description = ''
63+
User to role mapping.
64+
'';
65+
};
66+
67+
roles = lib.mkOption {
68+
type = lib.types.attrsOf (
69+
lib.types.submodule {
70+
options = {
71+
paths = lib.mkOption {
72+
type = lib.types.str;
73+
default = ".*";
74+
example = "^(?:\/admin|\/secret)(?:\?.*)?$";
75+
description = ''
76+
Regex of paths this role has access to.
77+
'';
78+
};
79+
};
80+
}
81+
);
82+
default = { };
83+
example = {
84+
"user" = {
85+
paths = "^\/user\/profile(?:\?.*)?$";
86+
};
87+
"admin" = { };
88+
};
89+
description = ''
90+
Role to allowed paths mapping.
91+
'';
5992
};
60-
description = ''
61-
Users to which access is granted.
62-
'';
6393
};
6494

6595
paths = lib.mkOption {
@@ -248,6 +278,15 @@ in
248278
The subpath used for xnode-auth endpoints.
249279
'';
250280
};
281+
282+
loginPage = lib.mkOption {
283+
type = lib.types.str;
284+
default = "/xnode-auth";
285+
example = "/xnode-monetization";
286+
description = ''
287+
The subpath to redirect unauthenticated users to.
288+
'';
289+
};
251290
};
252291
};
253292
};
@@ -367,7 +406,7 @@ in
367406
'';
368407
};
369408
"@login" = {
370-
return = "302 $scheme://$host${cfg.nginxConfig.subpath}?redirect=$scheme://$host$request_uri&rejected=$auth_resp_xnode_auth_deny_reason";
409+
return = "302 $scheme://$host${cfg.nginxConfig.loginPage}?redirect=$scheme://$host$request_uri&rejected=$auth_resp_xnode_auth_deny_reason";
371410
};
372411
}
373412
];

nix/package.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{ pkgs }:
22
pkgs.buildNpmPackage {
33
pname = "xnode-auth";
4-
version = "1.2.0";
4+
version = "2.0.0";
55
src = ../astro-app;
66

77
npmDeps = pkgs.importNpmLock {

0 commit comments

Comments
 (0)