@cueloop/schema 0.1.0-alpha.75 → 0.1.0-alpha.77
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 +1 -1
- package/src/index.ts +1 -0
- package/src/participants.test.ts +29 -0
- package/src/participants.ts +18 -7
- package/src/share-access.test.ts +20 -0
- package/src/share-access.ts +13 -0
- package/src/types.ts +9 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cueloop/schema",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.77",
|
|
4
4
|
"description": "The Thread primitive: types, markdown block model, quote-primary anchors, feedback serialization",
|
|
5
5
|
"homepage": "https://github.com/mmurakaru/cueloop#readme",
|
|
6
6
|
"bugs": {
|
package/src/index.ts
CHANGED
package/src/participants.test.ts
CHANGED
|
@@ -55,4 +55,33 @@ describe("registerParticipant", () => {
|
|
|
55
55
|
// Assert
|
|
56
56
|
expect(next.participants).toEqual([{ id: "SHA256:ana", provider: "ssh", name: "Ana" }]);
|
|
57
57
|
});
|
|
58
|
+
|
|
59
|
+
test("records a verified github source with its login handle", () => {
|
|
60
|
+
// Act
|
|
61
|
+
const next = registerParticipant(emptySession(), "SHA256:ana", "Ana", {
|
|
62
|
+
provider: "github",
|
|
63
|
+
handle: "ana",
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Assert
|
|
67
|
+
expect(next.participants).toEqual([
|
|
68
|
+
{ id: "SHA256:ana", provider: "github", name: "Ana", handle: "ana" },
|
|
69
|
+
]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("a later nameless write preserves the github provider and handle", () => {
|
|
73
|
+
// Arrange
|
|
74
|
+
const verified = registerParticipant(emptySession(), "SHA256:ana", "Ana", {
|
|
75
|
+
provider: "github",
|
|
76
|
+
handle: "ana",
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Act
|
|
80
|
+
const next = registerParticipant(verified, "SHA256:ana");
|
|
81
|
+
|
|
82
|
+
// Assert
|
|
83
|
+
expect(next.participants).toEqual([
|
|
84
|
+
{ id: "SHA256:ana", provider: "github", name: "Ana", handle: "ana" },
|
|
85
|
+
]);
|
|
86
|
+
});
|
|
58
87
|
});
|
package/src/participants.ts
CHANGED
|
@@ -13,17 +13,28 @@ import type { Identity, Thread } from "./types";
|
|
|
13
13
|
* rail renders anonymous) and never erases a name a past visit set. Immutable:
|
|
14
14
|
* the input session is not mutated.
|
|
15
15
|
*/
|
|
16
|
-
export
|
|
16
|
+
export interface ParticipantSource {
|
|
17
|
+
provider: "ssh" | "github";
|
|
18
|
+
handle?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function registerParticipant(
|
|
22
|
+
session: Thread,
|
|
23
|
+
author: string,
|
|
24
|
+
name?: string,
|
|
25
|
+
source?: ParticipantSource,
|
|
26
|
+
): Thread {
|
|
17
27
|
const participants = session.participants ?? [];
|
|
18
28
|
const existing = participants.find((participant) => participant.id === author);
|
|
19
29
|
const trimmed = name?.trim();
|
|
20
30
|
|
|
21
|
-
if (existing && !trimmed) return session;
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
31
|
+
if (existing && !trimmed && !source) return session;
|
|
32
|
+
const resolvedName = trimmed ?? existing?.name;
|
|
33
|
+
const resolvedHandle = source?.handle ?? existing?.handle;
|
|
34
|
+
const next: Identity = { id: author, provider: source?.provider ?? existing?.provider ?? "ssh" };
|
|
35
|
+
|
|
36
|
+
if (resolvedName) next.name = resolvedName;
|
|
37
|
+
if (resolvedHandle) next.handle = resolvedHandle;
|
|
27
38
|
|
|
28
39
|
return {
|
|
29
40
|
...session,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { isShareViewerAllowed } from "./share-access";
|
|
3
|
+
|
|
4
|
+
describe("isShareViewerAllowed", () => {
|
|
5
|
+
test("an unauthenticated viewer is refused", () => {
|
|
6
|
+
expect(isShareViewerAllowed({ githubLogins: ["octocat"] }, undefined)).toBe(false);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test("a member is admitted, case-insensitively", () => {
|
|
10
|
+
expect(isShareViewerAllowed({ githubLogins: ["OctoCat"] }, "octocat")).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("a non-member is refused", () => {
|
|
14
|
+
expect(isShareViewerAllowed({ githubLogins: ["octocat"] }, "hubot")).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("an empty allowlist admits no one", () => {
|
|
18
|
+
expect(isShareViewerAllowed({ githubLogins: [] }, "octocat")).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Whether a github login may view a private share; membership is case-insensitive and an unauthenticated viewer is never allowed. */
|
|
2
|
+
|
|
3
|
+
import type { ShareAccess } from "./types";
|
|
4
|
+
|
|
5
|
+
export function isShareViewerAllowed(
|
|
6
|
+
access: ShareAccess,
|
|
7
|
+
githubLogin: string | undefined,
|
|
8
|
+
): boolean {
|
|
9
|
+
if (githubLogin === undefined) return false;
|
|
10
|
+
const login = githubLogin.toLowerCase();
|
|
11
|
+
|
|
12
|
+
return access.githubLogins.some((allowed) => allowed.toLowerCase() === login);
|
|
13
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -224,14 +224,19 @@ export type SessionStatus = "pending" | "resolved";
|
|
|
224
224
|
export interface Identity {
|
|
225
225
|
/** Stable identity key; equals an annotation's `author`. */
|
|
226
226
|
id: string;
|
|
227
|
-
/** Identity source
|
|
228
|
-
provider: "ssh";
|
|
227
|
+
/** Identity source: the SSH key that authored, or a verified GitHub login. */
|
|
228
|
+
provider: "ssh" | "github";
|
|
229
229
|
/** Display name; absent = the collaborator stayed anonymous. */
|
|
230
230
|
name?: string;
|
|
231
231
|
/** Provider handle: a github login, an email, or a short fingerprint. */
|
|
232
232
|
handle?: string;
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
+
/** Owner-set access control for a private share: only these GitHub logins may open it. Absent = a public share. */
|
|
236
|
+
export interface ShareAccess {
|
|
237
|
+
githubLogins: string[];
|
|
238
|
+
}
|
|
239
|
+
|
|
235
240
|
export interface Thread {
|
|
236
241
|
schemaVersion: string;
|
|
237
242
|
id: string;
|
|
@@ -278,6 +283,8 @@ export interface Thread {
|
|
|
278
283
|
shareBranch?: string;
|
|
279
284
|
/** SSH fingerprint that created the share; the gateway stamps it to gate pulls. */
|
|
280
285
|
owner?: string;
|
|
286
|
+
/** Owner-set allowlist of GitHub logins for a private share; absent = public. */
|
|
287
|
+
access?: ShareAccess;
|
|
281
288
|
/**
|
|
282
289
|
* Identities that authored annotations here, keyed by id (union-by-id, like
|
|
283
290
|
* annotations). The gateway records a collaborator's identity and chosen name;
|