From 1db2acb29e36e8118335224bf3b66beecc885530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Tue, 11 Mar 2025 10:59:17 -0400 Subject: [PATCH] input and output definitions --- .../src/tools/achievements/event-collector.ts | 0 .../src/tools/achievements/event-processor.ts | 0 .../src/tools/achievements/event-store.ts | 0 libs/common/src/tools/achievements/events.ts | 104 ++++++++++++++++++ libs/common/src/tools/achievements/inputs.ts | 28 +++++ libs/common/src/tools/achievements/types.ts | 32 ++++++ libs/common/src/tools/log/ecs-format/core.ts | 35 ++++-- 7 files changed, 188 insertions(+), 11 deletions(-) create mode 100644 libs/common/src/tools/achievements/event-collector.ts create mode 100644 libs/common/src/tools/achievements/event-processor.ts create mode 100644 libs/common/src/tools/achievements/event-store.ts create mode 100644 libs/common/src/tools/achievements/events.ts create mode 100644 libs/common/src/tools/achievements/inputs.ts create mode 100644 libs/common/src/tools/achievements/types.ts diff --git a/libs/common/src/tools/achievements/event-collector.ts b/libs/common/src/tools/achievements/event-collector.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libs/common/src/tools/achievements/event-processor.ts b/libs/common/src/tools/achievements/event-processor.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libs/common/src/tools/achievements/event-store.ts b/libs/common/src/tools/achievements/event-store.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libs/common/src/tools/achievements/events.ts b/libs/common/src/tools/achievements/events.ts new file mode 100644 index 00000000000..20e32a10dc4 --- /dev/null +++ b/libs/common/src/tools/achievements/events.ts @@ -0,0 +1,104 @@ +import { of } from "rxjs"; + +const itemAdded$ = of({ + "@timestamp": Date.now(), + user: { + id: "1E2EDBC3-4449-4583-A4AC-ACDFA5C2EC71", + }, + event: { + kind: "event", + category: "session", + type: "creation", + outcome: "success", + provider: "vault", + }, + service: { + name: "chrome-extension", + type: "client", + node: { name: "commotion-amused-rinse-trivial-sadly" }, + environment: "production", + version: "37899", + }, + transaction: { id: "00f067aa0ba902b7" }, + action: "vault-item-added", + labels: { "vault-item-type": "login", "vault-item-uri-quantity": 1 }, + tags: ["with-attachment"], +}); + +const itemUpdated$ = of({ + "@timestamp": Date.now(), + user: { + id: "1E2EDBC3-4449-4583-A4AC-ACDFA5C2EC71", + }, + event: { + kind: "event", + category: "session", + type: "creation", + outcome: "success", + provider: "bitwarden-chrome-extension", + }, + service: { + name: "chrome-extension", + type: "client", + node: { name: "commotion-amused-rinse-trivial-sadly" }, + environment: "production", + version: "37899", + }, + transaction: { id: "00f067aa0ba902b8" }, + action: "vault-item-updated", + labels: { "vault-item-type": "login", "uri-quantity": 1 }, + tags: ["with-folder"], +}); + +const itemMovedToCollection$ = of( + { + "@timestamp": Date.now(), + user: { + id: "1E2EDBC3-4449-4583-A4AC-ACDFA5C2EC71", + }, + event: { + kind: "event", + category: "session", + type: "deletion", + outcome: "success", + provider: "bitwarden-chrome-extension", + }, + service: { + name: "chrome-extension", + type: "client", + node: { name: "commotion-amused-rinse-trivial-sadly" }, + environment: "production", + version: "37899", + }, + transaction: { id: "00f067aa0ba902b9" }, + action: "vault-item-moved", + labels: { "vault-item-type": "login", "uri-quantity": 1 }, + tags: ["collection"], + }, + { + "@timestamp": Date.now(), + user: { + id: "1E2EDBC3-4449-4583-A4AC-ACDFA5C2EC71", + }, + event: { + kind: "event", + category: "session", + type: "info", + outcome: "success", + provider: "bitwarden-chrome-extension", + }, + service: { + name: "chrome-extension", + type: "client", + node: { name: "commotion-amused-rinse-trivial-sadly" }, + environment: "production", + version: "37899", + }, + transaction: { id: "00f067aa0ba902b9" }, + action: "vault-item-moved", + labels: { "vault-item-type": "login", "uri-quantity": 1 }, + tags: ["collection"], + }, +); + +export { itemAdded$, itemUpdated$, itemMovedToCollection$ }; diff --git a/libs/common/src/tools/achievements/inputs.ts b/libs/common/src/tools/achievements/inputs.ts new file mode 100644 index 00000000000..2700e4c09af --- /dev/null +++ b/libs/common/src/tools/achievements/inputs.ts @@ -0,0 +1,28 @@ +import { Subject } from "rxjs"; + +import { EventFormat } from "../log/ecs-format"; + +import { Achievement, AchievementFormat, AchievementWatch } from "./types"; + +// sync data from the server (consumed by event store) +const replicationIn$ = new Subject(); + +// data incoming from the UI (consumed by validator) +const userActionIn$ = new Subject(); + +// what to look for (consumed by validator) +const achievementMonitors$ = new Subject(); + +// data stored in local state (consumed by validator and achievement list) +const achievementsLocal$ = new Subject(); + +// metadata (consumed by achievement list) +const achievementMetadata$ = new Subject(); + +export { + replicationIn$, + userActionIn$, + achievementsLocal$, + achievementMonitors$, + achievementMetadata$, +}; diff --git a/libs/common/src/tools/achievements/types.ts b/libs/common/src/tools/achievements/types.ts new file mode 100644 index 00000000000..3f606ee5dd6 --- /dev/null +++ b/libs/common/src/tools/achievements/types.ts @@ -0,0 +1,32 @@ +import { RequireAtLeastOne } from "type-fest"; +import { Tagged } from "type-fest/source/opaque"; + +import { EcsFormat, EventFormat } from "../log/ecs-format"; + +export type AchievementId = string & Tagged<"achievement">; + +type Progress = { type: "progress"; name: AchievementId; value: number }; +type Earned = { type: "earned"; name: AchievementId }; +export type AchievementFormat = EcsFormat & EventFormat & { achievement: Progress | Earned }; + +// consumed by validator and achievement list (should this include a "toast-alerter"?) +export type Achievement = { + achievement: AchievementId; + + // pre-filter that disables the rule if it's met + trigger: "once" | RequireAtLeastOne<{ low: number; high: number }>; + + hidden: boolean; +}; + +// consumed by validator +export type AchievementWatch = Achievement & { + // when the watch triggers on incoming user events + filter: (item: EventFormat) => boolean; + + // what to do when an incoming event is triggered + action: ( + item: EventFormat, + progress?: AchievementFormat, + ) => [AchievementFormat] | [AchievementFormat, AchievementFormat]; +}; diff --git a/libs/common/src/tools/log/ecs-format/core.ts b/libs/common/src/tools/log/ecs-format/core.ts index 3667be590d5..18cf72e4b20 100644 --- a/libs/common/src/tools/log/ecs-format/core.ts +++ b/libs/common/src/tools/log/ecs-format/core.ts @@ -1,22 +1,35 @@ +import { Primitive } from "type-fest"; + /** Elastic Common Schema log format - core fields. */ export interface EcsFormat { - "@timestamp": Date, + "@timestamp": Date; /** custom key/value pairs */ - labels?: Record, + labels?: Record; - /** system message related to the event */ - message?: string, + /** system message related to the event (for humans) */ + message?: string; /** keywords tagging the event */ - tags?: Array, + tags?: Array; /** describe the event; it is recommended that all events have these. */ event: { - kind?: "alert" | "enrichment" | "event" | "metric" | "state", - category?: "api" | "authentication" | "iam" | "process" | "session" , - type?: "access" | "admin" | "allowed" | "creation" | "deletion" | "denied" | "end" | "error" | "info" | "start" | "user", - outcome?: "failure" | "success" | "unknown", - } -}; + kind?: "alert" | "enrichment" | "event" | "metric" | "state"; + category?: "api" | "authentication" | "iam" | "process" | "session"; + type?: + | "access" + | "admin" + | "allowed" + | "creation" + | "deletion" + | "denied" + | "end" + | "error" + | "info" + | "start" + | "user"; + outcome?: "failure" | "success" | "unknown"; + }; +}