anbaric-cloud-hosting 1.62.0 → 1.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anbaric-cloud-hosting",
3
- "version": "1.62.0",
3
+ "version": "1.63.0",
4
4
  "description": "Anbaric Cloud hosting service: Postgres-backed job persistence and queuing exposed over an HTTP API",
5
5
  "license": "MIT",
6
6
  "author": "chris@anbaric.ai",
@@ -18,18 +18,18 @@
18
18
  "@aws-sdk/client-s3": "^3.1110.0",
19
19
  "@aws-sdk/client-secrets-manager": "^3.700.0",
20
20
  "@aws-sdk/client-servicediscovery": "^3.1110.0",
21
- "anbaric-data-store": "^1.62.0",
22
- "anbaric-plugins": "^1.62.0",
23
- "anbaric-tsapi": "^1.62.0",
24
- "anbaric-web": "^1.62.0",
21
+ "anbaric-data-store": "^1.63.0",
22
+ "anbaric-plugins": "^1.63.0",
23
+ "anbaric-tsapi": "^1.63.0",
24
+ "anbaric-web": "^1.63.0",
25
25
  "esbuild": "^0.28.2",
26
26
  "pg": "^8.16.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^26.2.0",
30
30
  "@types/pg": "^8.15.0",
31
- "anbaric-impl-cloud": "^1.62.0",
32
- "anbaric-state-machine": "^1.62.0",
31
+ "anbaric-impl-cloud": "^1.63.0",
32
+ "anbaric-state-machine": "^1.63.0",
33
33
  "tsx": "^4.20.0",
34
34
  "typescript": "^7.0.2"
35
35
  },
@@ -46,6 +46,12 @@ class HttpMembershipService implements MembershipService {
46
46
  return response.status === 204;
47
47
  }
48
48
 
49
+ async remove(userId : string, asking : Inviter) : Promise<boolean> {
50
+ const response = await this.call("DELETE", `/members/${encodeURIComponent(userId)}`, undefined, [404], asking);
51
+ this.roles.delete(userId);
52
+ return response.status === 204;
53
+ }
54
+
49
55
  private async call(method : string, path : string, body? : unknown,
50
56
  tolerated : Array<number> = [], asking? : Inviter) : Promise<Response> {
51
57
  const headers : Record<string, string> = { "x-anbaric-central-key": this.secret };
@@ -17,6 +17,10 @@ class InMemoryMembershipService implements MembershipService {
17
17
  return this.members.get(userId);
18
18
  }
19
19
 
20
+ async remove(userId : string, _asking : Inviter) : Promise<boolean> {
21
+ return this.members.delete(userId);
22
+ }
23
+
20
24
  async invite(email : string, role : TenantRole, invitedBy : Inviter, landingApp? : string) : Promise<PendingInvitation> {
21
25
  const token = randomUUID();
22
26
  const now = new Date();
@@ -39,6 +39,9 @@ interface MembershipService {
39
39
 
40
40
  revoke(token : string, asking : Inviter) : Promise<boolean>;
41
41
 
42
+ // Ends someone's membership. False when they were not a member.
43
+ remove(userId : string, asking : Inviter) : Promise<boolean>;
44
+
42
45
  }
43
46
 
44
47
  export type { MembershipService, PendingInvitation, Inviter }
@@ -10,6 +10,7 @@ import {AppProxyHandler} from "./handlers/AppProxyHandler";
10
10
  import {AppLinkFallbackHandler} from "./handlers/AppLinkFallbackHandler";
11
11
  import {AppsHandler} from "./handlers/AppsHandler";
12
12
  import {MyAppsHandler} from "./handlers/MyAppsHandler";
13
+ import {MembersHandler} from "./handlers/auth/MembersHandler";
13
14
  import {Subdomains} from "../subdomains/Subdomains";
14
15
  import {AuditsHandler} from "./handlers/AuditsHandler";
15
16
  import {AuthorizeCliHandler} from "./handlers/auth/AuthorizeCliHandler";
@@ -111,6 +112,7 @@ class HostingServer {
111
112
  if (entitlements) publicRouter.registerApi("entitlements", new EntitlementsAdminHandler(entitlements));
112
113
  if (userDirectory) publicRouter.registerApi("users", new UsersHandler(userDirectory));
113
114
  if (memberships) publicRouter.registerApi("invitations", new InvitationsHandler(memberships, entitlements));
115
+ if (memberships) publicRouter.registerApi("members", new MembersHandler(memberships, entitlements));
114
116
  const prompts = promptManagerFor && new PromptsHandler(promptManagerFor);
115
117
  if (prompts) publicRouter.registerApi("prompts", prompts);
116
118
  if (cliAuthorizer) {
@@ -0,0 +1,43 @@
1
+ import {MembershipService} from "../../../auth/MembershipService";
2
+ import {canInvite} from "../../../auth/TenantRole";
3
+ import {EntitlementStore} from "../../../data-store/EntitlementStore";
4
+ import {Request} from "../../Request";
5
+ import {RequestHandler} from "../../RequestHandler";
6
+
7
+ /* The console's side of removing someone from this tenant. Membership is the
8
+ control plane's to end - it is what admits a session at all - and it is
9
+ asked to; what is this platform's own is the entitlements that person held
10
+ here, which go with them so nothing is left granted to somebody who can no
11
+ longer sign in. Their directory entry stays: it is a record of who was here. */
12
+ class MembersHandler implements RequestHandler {
13
+
14
+ constructor(private memberships : MembershipService, private entitlements? : EntitlementStore) {}
15
+
16
+ async handle(request : Request) : Promise<void> {
17
+ if (request.method !== "DELETE" || ! request.id || request.subresource) return request.notFound();
18
+
19
+ if (! canInvite(request.user?.tenantRole)) {
20
+ return request.reply(403, { error: "Your role in this tenant cannot remove members" });
21
+ }
22
+ if (request.user?.id === request.id) {
23
+ return request.reply(400, { error: "You cannot remove yourself from a tenant" });
24
+ }
25
+
26
+ const asking = request.user ? { id: request.user.id, name: request.user.name } : { id: "system" };
27
+ let removed : boolean;
28
+ try {
29
+ removed = await this.memberships.remove(request.id, asking);
30
+ } catch (error) {
31
+ return request.reply(403, { error: error instanceof Error ? error.message : "Could not remove that member" });
32
+ }
33
+ if (! removed) return request.notFound();
34
+
35
+ for (const grant of await this.entitlements?.listGrants(request.id) ?? []) {
36
+ await this.entitlements?.revoke(grant.id);
37
+ }
38
+ request.reply(204);
39
+ }
40
+
41
+ }
42
+
43
+ export { MembersHandler }