slotlock 0.9.0 → 0.10.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/CHANGELOG.md +305 -0
- package/NOTICE +2 -0
- package/README.md +4 -2
- package/agent-store-backend.js +209 -15
- package/application-api.d.ts +10 -1
- package/application-api.js +213 -2
- package/availability-store.js +13 -5
- package/booking-rules.d.ts +11 -0
- package/booking-rules.js +21 -1
- package/config.d.ts +50 -0
- package/config.js +107 -0
- package/dashboard-scope.d.ts +7 -0
- package/dashboard-scope.js +21 -0
- package/dashboard.d.ts +97 -2
- package/dashboard.js +3068 -284
- package/ddl-identity.d.ts +25 -0
- package/ddl-identity.js +881 -0
- package/ddl-scim.d.ts +5 -0
- package/ddl-scim.js +740 -0
- package/ddl-sso.d.ts +15 -0
- package/ddl-sso.js +1033 -0
- package/ddl.d.ts +40 -9
- package/ddl.js +2123 -49
- package/domains.d.ts +117 -0
- package/domains.js +340 -0
- package/event-feed.d.ts +1 -1
- package/event-feed.js +6 -34
- package/holiday-import.d.ts +1 -1
- package/holiday-import.js +2 -3
- package/holidays.js +70 -11
- package/index.d.ts +12 -4
- package/index.js +8 -2
- package/invitation-email.d.ts +26 -0
- package/invitation-email.js +73 -0
- package/maintenance.d.ts +7 -2
- package/maintenance.js +15 -1
- package/oidc.d.ts +135 -0
- package/oidc.js +452 -0
- package/openapi.json +1886 -6
- package/package.json +2 -1
- package/scim-server.d.ts +19 -0
- package/scim-server.js +243 -0
- package/scim.d.ts +180 -0
- package/scim.js +416 -0
- package/self-host.d.ts +19 -6
- package/self-host.js +262 -7
- package/slotlock.manifest.json +1 -1
- package/store.d.ts +273 -2
- package/store.js +984 -72
- package/types.d.ts +33 -1
- package/types.js +68 -1
- package/workspace-domains.d.ts +146 -0
- package/workspace-domains.js +249 -0
- package/workspace-roles.d.ts +47 -0
- package/workspace-roles.js +304 -0
- package/workspace-scim.d.ts +89 -0
- package/workspace-scim.js +157 -0
- package/workspace-sso.d.ts +131 -0
- package/workspace-sso.js +180 -0
- package/workspaces.d.ts +240 -0
- package/workspaces.js +433 -0
package/index.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export { maintainSlotlockTenant, startSlotlockMaintenance, withSlotlockMaintenan
|
|
|
36
36
|
export type { SlotlockMaintenanceResult, SlotlockMaintenanceScheduler, SlotlockMaintenanceSchedulerOptions, } from './maintenance.js';
|
|
37
37
|
export { createSlotlockApiKeyAuthenticator, createSlotlockApiKeyStore, generateSlotlockApiKey, isSlotlockApiKey, isSlotlockTestTenant, slotlockLiveTenant, SLOTLOCK_TEST_API_KEY_PREFIX, SLOTLOCK_TEST_TENANT_SUFFIX, slotlockApiKeyDigest, SLOTLOCK_API_KEY_ACTIVE_LIMIT, SLOTLOCK_API_KEY_MAX_LIFETIME_DAYS, SLOTLOCK_API_KEY_PREFIX, SLOTLOCK_API_KEY_RETAINED_LIMIT, SLOTLOCK_API_KEY_SCOPES, } from './api-keys.js';
|
|
38
38
|
export { createSlotlockDashboard, createSlotlockDashboardResources, createSlotlockAcceptanceRecords, createSlotlockDashboardState, createSlotlockMemoryDashboardState, SLOTLOCK_DASHBOARD_MAX_RESOURCES, } from './dashboard.js';
|
|
39
|
-
export type { SlotlockDashboard, SlotlockDashboardAssistantOptions, SlotlockDashboardOptions, SlotlockDashboardResources, SlotlockDashboardState, } from './dashboard.js';
|
|
39
|
+
export type { SlotlockDashboard, SlotlockDashboardAssistantOptions, SlotlockDashboardOptions, SlotlockDashboardResources, SlotlockDashboardState, SlotlockInvitationSender, } from './dashboard.js';
|
|
40
40
|
export { createSlotlockAssistantStore, createSlotlockMemoryAssistantStore, SLOTLOCK_ASSISTANT_TOOL_USE, } from './assistant.js';
|
|
41
41
|
export type { SlotlockAssistantStore } from './assistant.js';
|
|
42
42
|
export { createSlotlockPartnerKeyAuthenticator, createSlotlockPartnerStore, parseSlotlockPartnerContract, slotlockPartnerTenantRef, SLOTLOCK_PARTNER_ACCOUNT, SLOTLOCK_PARTNER_ID, SLOTLOCK_PARTNER_SCOPE, } from './partners.js';
|
|
@@ -46,6 +46,13 @@ export type { SlotlockOverageReport } from './overage.js';
|
|
|
46
46
|
export { createSlotlockOtlpExporter, readSlotlockOtlpConfig, slotlockCallSpan } from './otel.js';
|
|
47
47
|
export type { SlotlockOtlpConfig, SlotlockTraceExporter } from './otel.js';
|
|
48
48
|
export type { SlotlockDashboardTeams } from './teams.js';
|
|
49
|
+
export { createSlotlockInvitationEmailSender, SlotlockInvitationEmailError, slotlockInvitationEmail, } from './invitation-email.js';
|
|
50
|
+
export type { SlotlockInvitationEmailConfig } from './invitation-email.js';
|
|
51
|
+
export { createSlotlockDashboardWorkspaces, createSlotlockMemoryDashboardWorkspaces, createSlotlockWorkspaceCheck, } from './workspaces.js';
|
|
52
|
+
export type { SlotlockDashboardWorkspaces, SlotlockGitHubMembership, SlotlockInvitationPreview, SlotlockTeamRefusal, SlotlockTeamWorkspace, SlotlockWorkspaceInvitation, SlotlockWorkspaceMember, SlotlockWorkspaceStanding, SlotlockWorkspaceTeams, SlotlockDashboardSession, SlotlockDashboardSessions, } from './workspaces.js';
|
|
53
|
+
export { SLOTLOCK_DASHBOARD_PAGE_CAPABILITIES, SLOTLOCK_DASHBOARD_POST_CAPABILITIES, SLOTLOCK_FRESH_CAPABILITIES, SLOTLOCK_ROLE_CAPABILITIES, SLOTLOCK_SCOPED_CAPABILITIES, SLOTLOCK_SCOPED_DASHBOARD_PAGES, SLOTLOCK_SCOPED_DASHBOARD_POSTS, SLOTLOCK_WORKSPACE_CAPABILITIES, SLOTLOCK_WORKSPACE_ROLE_NAMES, SLOTLOCK_WORKSPACE_ROLES, isSlotlockWorkspaceRole, slotlockMemberCan, slotlockRoleCan, slotlockRoleKeyScopes, slotlockRoleMayGrant, } from './workspace-roles.js';
|
|
54
|
+
export { slotlockDashboardRequestScope } from './dashboard-scope.js';
|
|
55
|
+
export type { SlotlockWorkspaceCapability, SlotlockWorkspaceRole } from './workspace-roles.js';
|
|
49
56
|
export { closeSlotlockPartnerMonth, priceSlotlockPartnerMonth, slotlockClientLabel, slotlockPartnerMonthChecksum, } from './partner-billing.js';
|
|
50
57
|
export type { SlotlockPartnerAccountMonth, SlotlockPartnerInvoiceLine, SlotlockPartnerMonthClose, } from './partner-billing.js';
|
|
51
58
|
export type { SlotlockPartnerAccount, SlotlockPartnerContract, SlotlockPartnerPlan, SlotlockPartnerPricing, SlotlockPartnerStore, } from './partners.js';
|
|
@@ -54,6 +61,7 @@ export { ICalendarWorkerBusyError, ICalendarWorkerPoolClosedError, createICalend
|
|
|
54
61
|
export type { ICalendarWorkerPool, ICalendarWorkerPoolOptions } from './ical-worker.js';
|
|
55
62
|
export { CalendarContractError, CalendarExpansionLimitError, emitICalendar, emitITipCalendar, expandCalendarEventOccurrences, normalizeCalendarEventContent, normalizeExternalCalendarChange, parseICalendarChanges, parseICalendarFeed, parseTrustedICalendarEvents, recurrenceRuleProblem, storedCalendarEventProblem, } from './sync.js';
|
|
56
63
|
export type { ExternalCalendarChange, ExternalCalendarChangeInput, ExternalCalendarStatus, ICalendarEvent, ICalendarExpansionWindow, ICalendarFeedRead, ICalendarSeriesRefusal, ITipMethod, } from './sync.js';
|
|
57
|
-
export { createSlotlockApplicationRoleGrantsDdl, createSlotlockTenantRlsDdl, SLOTLOCK_API_KEY_FUNCTIONS, SLOTLOCK_API_KEY_TABLES, SLOTLOCK_CORE_DDL, SLOTLOCK_DASHBOARD_FUNCTIONS, SLOTLOCK_DASHBOARD_TABLES, SLOTLOCK_DEFINER_FUNCTIONS, SLOTLOCK_DEFINER_TABLES, SLOTLOCK_OPERATOR_FUNCTIONS, SLOTLOCK_SCHEMA_FUNCTIONS, SLOTLOCK_SCHEMA_TABLES, SLOTLOCK_SCHEMA_VERSION, SLOTLOCK_TENANT_ADMIN_FUNCTIONS, SLOTLOCK_TENANT_ADMIN_TABLES, SLOTLOCK_TENANT_CONTEXT_SETTING, SLOTLOCK_TENANT_RLS_DDL, SLOTLOCK_TENANT_TABLES, } from './ddl.js';
|
|
58
|
-
export { assertSlotlockSchemaCurrent, calendarEventRollingHorizon, createSlotlockStore, readSlotlockSchemaVersion, SLOTLOCK_AGENT_OWNER_COMMAND_QUOTA, SLOTLOCK_AGENT_OWNER_EVENT_QUOTA, SLOTLOCK_CALENDAR_HORIZON_DAYS, SLOTLOCK_SOURCE_BUSY_MAX, SLOTLOCK_EVENT_COMMAND_RETENTION_DAYS, SLOTLOCK_INTERNAL_EVENT_OWNER_REF, SLOTLOCK_LOCAL_RECURRENCE_COVERAGE_SOURCE, SLOTLOCK_MATERIALIZATION_WINDOW_DAYS, SLOTLOCK_MAX_EVENT_DURATION_DAYS, SLOTLOCK_MAX_CONTACTS_PER_TENANT, SLOTLOCK_PAYMENT_HOLD_MAX_TTL_MS, SLOTLOCK_TENANT_WRITE_METHODS, type SlotlockHoldTenantOptions, type SlotlockMigrationOptions, type SlotlockSql, type SlotlockStore, type SlotlockStoreOptions, type SlotlockTenantLimits, type SlotlockTenantStore, } from './store.js';
|
|
59
|
-
export type { AcquireHoldResult, BookingRetentionResult, CalendarAttendee, CalendarAttendeeRole, CalendarCoverageAssessment, CalendarCoverageCommand, CalendarCoverageResult, CalendarEventContent, CalendarEventOccurrence, CalendarEventRetentionResult, CalendarEventStatus, CalendarFreeBusy, CalendarHorizonRollCursor, CalendarHorizonRollResult, CalendarOrganizer, CalendarParticipationStatus, CalendarRecurrence, CalendarRecurrenceException, CalendarReminder, CalendarTransparency, ConfirmHoldResult, CancelCalendarEventResult, CancelExternalReservationResult, CreateReservationResult, DeleteResourceResult, ExternalReservationCommand, ExternalReservationResult, Interval, SlotlockAgendaEntry, SlotlockHold, MarkHoldPaidResult, SlotlockContact, SlotlockContactConsent, PutContactResult, SourceBusyCommand, SourceBusyResult, SlotlockCalendarEvent, SlotlockBookingDescription, SlotlockReservation, SlotlockResource, SlotlockTenantErasure, SlotlockBookingRuleDefaults, PutCalendarEventCommand, PutCalendarEventResult, ReleaseHoldResult, TrustedICalendarEvent, SlotlockInheritedAvailability, WeeklyAvailabilityRule, } from './types.js';
|
|
64
|
+
export { createSlotlockApplicationRoleGrantsDdl, createSlotlockTenantRlsDdl, SLOTLOCK_API_KEY_FUNCTIONS, SLOTLOCK_API_KEY_TABLES, SLOTLOCK_CORE_DDL, SLOTLOCK_DASHBOARD_FUNCTIONS, SLOTLOCK_DASHBOARD_TABLES, SLOTLOCK_APPEND_ONLY_TABLES, SLOTLOCK_AUDIT_RETENTION_DAYS, SLOTLOCK_CHANGE_RETENTION_BY_PLAN, SLOTLOCK_DEFINER_FUNCTIONS, SLOTLOCK_DEFINER_TABLES, SLOTLOCK_OPERATOR_FUNCTIONS, SLOTLOCK_SCHEMA_FUNCTIONS, SLOTLOCK_SCHEMA_TABLES, SLOTLOCK_SCHEMA_VERSION, SLOTLOCK_TENANT_ADMIN_FUNCTIONS, SLOTLOCK_TENANT_ADMIN_TABLES, SLOTLOCK_TENANT_CONTEXT_SETTING, SLOTLOCK_TENANT_RLS_DDL, SLOTLOCK_TENANT_TABLES, } from './ddl.js';
|
|
65
|
+
export { assertSlotlockSchemaCurrent, calendarEventRollingHorizon, createSlotlockStore, readSlotlockSchemaVersion, SLOTLOCK_AGENT_OWNER_COMMAND_QUOTA, SLOTLOCK_AGENT_OWNER_EVENT_QUOTA, SLOTLOCK_CALENDAR_HORIZON_DAYS, SLOTLOCK_SOURCE_BUSY_MAX, SLOTLOCK_EVENT_COMMAND_RETENTION_DAYS, SLOTLOCK_INTERNAL_EVENT_OWNER_REF, SLOTLOCK_LOCAL_RECURRENCE_COVERAGE_SOURCE, SLOTLOCK_MATERIALIZATION_WINDOW_DAYS, SLOTLOCK_MAX_EVENT_DURATION_DAYS, SLOTLOCK_MAX_CONTACTS_PER_TENANT, SLOTLOCK_PAYMENT_HOLD_MAX_TTL_MS, SLOTLOCK_TENANT_WRITE_METHODS, SLOTLOCK_TENANT_EXPORT_HOURS, SLOTLOCK_TENANT_EXPORT_MAX_BYTES, SLOTLOCK_TENANT_EXPORT_PART_ROWS, SLOTLOCK_MAX_TENANT_EXPORTS, buildSlotlockTenantExport, slotlockAuditEventRecord, type SlotlockAuditEvent, type SlotlockAuditEventInput, type SlotlockSubject, type SlotlockSubjectErasure, type SlotlockSubjectExport, type SlotlockTenantExport, type SlotlockTenantExportPartResult, type SlotlockHoldTenantOptions, type SlotlockMigrationOptions, type SlotlockSql, type SlotlockStore, type SlotlockStoreOptions, type SlotlockTenantLimits, type SlotlockTenantStore, } from './store.js';
|
|
66
|
+
export type { AcquireHoldResult, BookingRetentionResult, CalendarAttendee, CalendarAttendeeRole, CalendarCoverageAssessment, CalendarCoverageCommand, CalendarCoverageResult, CalendarEventContent, CalendarEventOccurrence, CalendarEventRetentionResult, CalendarEventStatus, CalendarFreeBusy, CalendarHorizonRollCursor, CalendarHorizonRollResult, CalendarOrganizer, CalendarParticipationStatus, CalendarRecurrence, CalendarRecurrenceException, CalendarReminder, CalendarTransparency, ConfirmHoldResult, CancelCalendarEventResult, CancelExternalReservationResult, CreateReservationResult, DeleteResourceResult, ExternalReservationCommand, ExternalReservationResult, Interval, SlotlockAgendaEntry, SlotlockHold, MarkHoldPaidResult, SlotlockContact, SlotlockContactConsent, PutContactResult, SourceBusyCommand, SourceBusyResult, SlotlockCalendarEvent, SlotlockBookingDescription, SlotlockReservation, SlotlockResource, SlotlockTenantErasure, SlotlockBookingRuleDefaults, PutCalendarEventCommand, PutCalendarEventResult, ReleaseHoldResult, TrustedICalendarEvent, SlotlockInheritedAvailability, WeeklyAvailabilityRule, SlotlockSubjectTable, SlotlockTenantExportTable, } from './types.js';
|
|
67
|
+
export { SLOTLOCK_SUBJECT_TABLES, SLOTLOCK_TENANT_EXPORT_LEFT_OUT, SLOTLOCK_TENANT_EXPORT_TABLES, } from './types.js';
|
package/index.js
CHANGED
|
@@ -34,8 +34,14 @@ export { createSlotlockPartnerKeyAuthenticator, createSlotlockPartnerStore, pars
|
|
|
34
34
|
export { createSlotlockDashboardTeams, createSlotlockOrganizationCheck } from './teams.js';
|
|
35
35
|
export { reportSlotlockOverage, SLOTLOCK_DEFAULT_OVERAGE_STOP_PERCENT } from './overage.js';
|
|
36
36
|
export { createSlotlockOtlpExporter, readSlotlockOtlpConfig, slotlockCallSpan } from './otel.js';
|
|
37
|
+
export { createSlotlockInvitationEmailSender, SlotlockInvitationEmailError, slotlockInvitationEmail, } from './invitation-email.js';
|
|
38
|
+
export { createSlotlockDashboardWorkspaces, createSlotlockMemoryDashboardWorkspaces, createSlotlockWorkspaceCheck, } from './workspaces.js';
|
|
39
|
+
export { SLOTLOCK_DASHBOARD_PAGE_CAPABILITIES, SLOTLOCK_DASHBOARD_POST_CAPABILITIES, SLOTLOCK_FRESH_CAPABILITIES, SLOTLOCK_ROLE_CAPABILITIES, SLOTLOCK_SCOPED_CAPABILITIES, SLOTLOCK_SCOPED_DASHBOARD_PAGES, SLOTLOCK_SCOPED_DASHBOARD_POSTS, SLOTLOCK_WORKSPACE_CAPABILITIES, SLOTLOCK_WORKSPACE_ROLE_NAMES, SLOTLOCK_WORKSPACE_ROLES, isSlotlockWorkspaceRole, slotlockMemberCan, slotlockRoleCan, slotlockRoleKeyScopes, slotlockRoleMayGrant, } from './workspace-roles.js';
|
|
40
|
+
// For resource adapters of one's own that keep to a limited member's scope (`keepsToScope`).
|
|
41
|
+
export { slotlockDashboardRequestScope } from './dashboard-scope.js';
|
|
37
42
|
export { closeSlotlockPartnerMonth, priceSlotlockPartnerMonth, slotlockClientLabel, slotlockPartnerMonthChecksum, } from './partner-billing.js';
|
|
38
43
|
export { ICalendarWorkerBusyError, ICalendarWorkerPoolClosedError, createICalendarWorkerPool, } from './ical-worker.js';
|
|
39
44
|
export { CalendarContractError, CalendarExpansionLimitError, emitICalendar, emitITipCalendar, expandCalendarEventOccurrences, normalizeCalendarEventContent, normalizeExternalCalendarChange, parseICalendarChanges, parseICalendarFeed, parseTrustedICalendarEvents, recurrenceRuleProblem, storedCalendarEventProblem, } from './sync.js';
|
|
40
|
-
export { createSlotlockApplicationRoleGrantsDdl, createSlotlockTenantRlsDdl, SLOTLOCK_API_KEY_FUNCTIONS, SLOTLOCK_API_KEY_TABLES, SLOTLOCK_CORE_DDL, SLOTLOCK_DASHBOARD_FUNCTIONS, SLOTLOCK_DASHBOARD_TABLES, SLOTLOCK_DEFINER_FUNCTIONS, SLOTLOCK_DEFINER_TABLES, SLOTLOCK_OPERATOR_FUNCTIONS, SLOTLOCK_SCHEMA_FUNCTIONS, SLOTLOCK_SCHEMA_TABLES, SLOTLOCK_SCHEMA_VERSION, SLOTLOCK_TENANT_ADMIN_FUNCTIONS, SLOTLOCK_TENANT_ADMIN_TABLES, SLOTLOCK_TENANT_CONTEXT_SETTING, SLOTLOCK_TENANT_RLS_DDL, SLOTLOCK_TENANT_TABLES, } from './ddl.js';
|
|
41
|
-
export { assertSlotlockSchemaCurrent, calendarEventRollingHorizon, createSlotlockStore, readSlotlockSchemaVersion, SLOTLOCK_AGENT_OWNER_COMMAND_QUOTA, SLOTLOCK_AGENT_OWNER_EVENT_QUOTA, SLOTLOCK_CALENDAR_HORIZON_DAYS, SLOTLOCK_SOURCE_BUSY_MAX, SLOTLOCK_EVENT_COMMAND_RETENTION_DAYS, SLOTLOCK_INTERNAL_EVENT_OWNER_REF, SLOTLOCK_LOCAL_RECURRENCE_COVERAGE_SOURCE, SLOTLOCK_MATERIALIZATION_WINDOW_DAYS, SLOTLOCK_MAX_EVENT_DURATION_DAYS, SLOTLOCK_MAX_CONTACTS_PER_TENANT, SLOTLOCK_PAYMENT_HOLD_MAX_TTL_MS, SLOTLOCK_TENANT_WRITE_METHODS, } from './store.js';
|
|
45
|
+
export { createSlotlockApplicationRoleGrantsDdl, createSlotlockTenantRlsDdl, SLOTLOCK_API_KEY_FUNCTIONS, SLOTLOCK_API_KEY_TABLES, SLOTLOCK_CORE_DDL, SLOTLOCK_DASHBOARD_FUNCTIONS, SLOTLOCK_DASHBOARD_TABLES, SLOTLOCK_APPEND_ONLY_TABLES, SLOTLOCK_AUDIT_RETENTION_DAYS, SLOTLOCK_CHANGE_RETENTION_BY_PLAN, SLOTLOCK_DEFINER_FUNCTIONS, SLOTLOCK_DEFINER_TABLES, SLOTLOCK_OPERATOR_FUNCTIONS, SLOTLOCK_SCHEMA_FUNCTIONS, SLOTLOCK_SCHEMA_TABLES, SLOTLOCK_SCHEMA_VERSION, SLOTLOCK_TENANT_ADMIN_FUNCTIONS, SLOTLOCK_TENANT_ADMIN_TABLES, SLOTLOCK_TENANT_CONTEXT_SETTING, SLOTLOCK_TENANT_RLS_DDL, SLOTLOCK_TENANT_TABLES, } from './ddl.js';
|
|
46
|
+
export { assertSlotlockSchemaCurrent, calendarEventRollingHorizon, createSlotlockStore, readSlotlockSchemaVersion, SLOTLOCK_AGENT_OWNER_COMMAND_QUOTA, SLOTLOCK_AGENT_OWNER_EVENT_QUOTA, SLOTLOCK_CALENDAR_HORIZON_DAYS, SLOTLOCK_SOURCE_BUSY_MAX, SLOTLOCK_EVENT_COMMAND_RETENTION_DAYS, SLOTLOCK_INTERNAL_EVENT_OWNER_REF, SLOTLOCK_LOCAL_RECURRENCE_COVERAGE_SOURCE, SLOTLOCK_MATERIALIZATION_WINDOW_DAYS, SLOTLOCK_MAX_EVENT_DURATION_DAYS, SLOTLOCK_MAX_CONTACTS_PER_TENANT, SLOTLOCK_PAYMENT_HOLD_MAX_TTL_MS, SLOTLOCK_TENANT_WRITE_METHODS, SLOTLOCK_TENANT_EXPORT_HOURS, SLOTLOCK_TENANT_EXPORT_MAX_BYTES, SLOTLOCK_TENANT_EXPORT_PART_ROWS, SLOTLOCK_MAX_TENANT_EXPORTS, buildSlotlockTenantExport, slotlockAuditEventRecord, } from './store.js';
|
|
47
|
+
export { SLOTLOCK_SUBJECT_TABLES, SLOTLOCK_TENANT_EXPORT_LEFT_OUT, SLOTLOCK_TENANT_EXPORT_TABLES, } from './types.js';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { SlotlockInvitationSender } from './dashboard.js';
|
|
2
|
+
/** Where invitations are sent from (SLOTLOCK_INVITATION_EMAIL, _KEY, _FROM). */
|
|
3
|
+
export interface SlotlockInvitationEmailConfig {
|
|
4
|
+
provider: 'resend' | 'postmark';
|
|
5
|
+
/** Resend's API key, or Postmark's server token. */
|
|
6
|
+
key: string;
|
|
7
|
+
/** The sender, as the provider has it verified: `Slotlock <team@example.com>` or an address. */
|
|
8
|
+
from: string;
|
|
9
|
+
}
|
|
10
|
+
type Invitation = Parameters<SlotlockInvitationSender['send']>[0];
|
|
11
|
+
/** A provider's refusal: its name and HTTP status alone (its text can echo the address). */
|
|
12
|
+
export declare class SlotlockInvitationEmailError extends Error {
|
|
13
|
+
name: string;
|
|
14
|
+
}
|
|
15
|
+
/** The message an invitation sends: subject, text and HTML saying the same, with no marketing. */
|
|
16
|
+
export declare function slotlockInvitationEmail(invitation: Invitation, brandName?: string): {
|
|
17
|
+
subject: string;
|
|
18
|
+
text: string;
|
|
19
|
+
html: string;
|
|
20
|
+
};
|
|
21
|
+
/** Sends each invitation once through the provider; throws on any answer but success. */
|
|
22
|
+
export declare function createSlotlockInvitationEmailSender(config: SlotlockInvitationEmailConfig, options?: {
|
|
23
|
+
fetch?: typeof fetch;
|
|
24
|
+
brandName?: string;
|
|
25
|
+
}): SlotlockInvitationSender;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Team invitations by email (dashboard.ts SlotlockInvitationSender): a plain transactional message
|
|
2
|
+
// with the link, sent through Resend or Postmark with the deployment's own key. Nothing else is
|
|
3
|
+
// sent, and no address is kept by this module.
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
/** A provider's refusal: its name and HTTP status alone (its text can echo the address). */
|
|
6
|
+
export class SlotlockInvitationEmailError extends Error {
|
|
7
|
+
name = 'SlotlockInvitationEmailError';
|
|
8
|
+
}
|
|
9
|
+
/** Resend refuses a request without a User-Agent (403), and a Worker's fetch sends none. */
|
|
10
|
+
const USER_AGENT = 'Slotlock (+https://slotlock.pylota.io)';
|
|
11
|
+
const escape = (value) => value.replace(/[&<>"']/g, (character) => `&#${character.charCodeAt(0)};`);
|
|
12
|
+
/** The message an invitation sends: subject, text and HTML saying the same, with no marketing. */
|
|
13
|
+
export function slotlockInvitationEmail(invitation, brandName = 'Slotlock') {
|
|
14
|
+
const until = `${invitation.expiresAt.toISOString().slice(0, 16).replace('T', ' ')} UTC`;
|
|
15
|
+
const subject = `${invitation.inviter} invited you to ${invitation.workspace} on ${brandName}`;
|
|
16
|
+
const lines = [
|
|
17
|
+
`${invitation.inviter} invited you to join ${invitation.workspace} on ${brandName} as ${invitation.role}.`,
|
|
18
|
+
`Join: ${invitation.link}`,
|
|
19
|
+
`The link works once, for this address, until ${until}. If you did not expect it, ignore this email: nothing happens unless you use the link.`,
|
|
20
|
+
];
|
|
21
|
+
const html = `<!doctype html><html><body style="font-family:system-ui,sans-serif;line-height:1.5;color:#111"><p>${escape(lines[0])}</p><p><a href="${escape(invitation.link)}">Join ${escape(invitation.workspace)}</a></p><p style="color:#555;font-size:14px">${escape(lines[2])}</p></body></html>`;
|
|
22
|
+
return { subject, text: `${lines.join('\n\n')}\n`, html };
|
|
23
|
+
}
|
|
24
|
+
/** Sends each invitation once through the provider; throws on any answer but success. */
|
|
25
|
+
export function createSlotlockInvitationEmailSender(config, options = {}) {
|
|
26
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
27
|
+
return {
|
|
28
|
+
async send(invitation) {
|
|
29
|
+
const message = slotlockInvitationEmail(invitation, options.brandName);
|
|
30
|
+
const request = config.provider === 'resend'
|
|
31
|
+
? {
|
|
32
|
+
url: 'https://api.resend.com/emails',
|
|
33
|
+
headers: {
|
|
34
|
+
authorization: `Bearer ${config.key}`,
|
|
35
|
+
'content-type': 'application/json',
|
|
36
|
+
'user-agent': USER_AGENT,
|
|
37
|
+
// One send per link, however often the person inviting submits: keyed by the link,
|
|
38
|
+
// as a digest, so the key names no address.
|
|
39
|
+
'idempotency-key': createHash('sha256').update(invitation.link, 'utf8').digest('hex'),
|
|
40
|
+
},
|
|
41
|
+
body: { from: config.from, to: [invitation.to], subject: message.subject, text: message.text, html: message.html },
|
|
42
|
+
}
|
|
43
|
+
: {
|
|
44
|
+
url: 'https://api.postmarkapp.com/email',
|
|
45
|
+
headers: {
|
|
46
|
+
'x-postmark-server-token': config.key,
|
|
47
|
+
'content-type': 'application/json',
|
|
48
|
+
accept: 'application/json',
|
|
49
|
+
'user-agent': USER_AGENT,
|
|
50
|
+
},
|
|
51
|
+
body: {
|
|
52
|
+
From: config.from,
|
|
53
|
+
To: invitation.to,
|
|
54
|
+
Subject: message.subject,
|
|
55
|
+
TextBody: message.text,
|
|
56
|
+
HtmlBody: message.html,
|
|
57
|
+
MessageStream: 'outbound',
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
const response = await fetchImpl(request.url, {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: request.headers,
|
|
63
|
+
body: JSON.stringify(request.body),
|
|
64
|
+
redirect: 'manual',
|
|
65
|
+
signal: AbortSignal.timeout(10_000),
|
|
66
|
+
});
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
// The status alone: a provider's error text can echo the address.
|
|
69
|
+
throw new SlotlockInvitationEmailError(`${config.provider === 'resend' ? 'Resend' : 'Postmark'} answered HTTP ${response.status}`);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
package/maintenance.d.ts
CHANGED
|
@@ -9,8 +9,8 @@ export interface SlotlockMaintenanceResult {
|
|
|
9
9
|
refused: number;
|
|
10
10
|
/**
|
|
11
11
|
* Rows deleted: expired holds and their keyed answers; past retention, event idempotency answers,
|
|
12
|
-
* agent event cancellations, stray booking retirements and feed changes;
|
|
13
|
-
* conversations. A cancelled booking's tombstone stays.
|
|
12
|
+
* agent event cancellations, stray booking retirements and feed changes; expired assistant
|
|
13
|
+
* conversations; and audit events past retention. A cancelled booking's tombstone stays.
|
|
14
14
|
*/
|
|
15
15
|
pruned: number;
|
|
16
16
|
/**
|
|
@@ -102,6 +102,11 @@ export interface SlotlockMaintenanceSchedulerOptions {
|
|
|
102
102
|
usage?: boolean;
|
|
103
103
|
/** Fetches holiday calendar subscriptions due to be imported again (maintainSlotlockTenant). */
|
|
104
104
|
holidayFetch?: typeof fetch;
|
|
105
|
+
/**
|
|
106
|
+
* Team workspaces' verified domains looked up again (workspace-domains.ts runSlotlockDomainChecks):
|
|
107
|
+
* run with each sweep, at most every quarter hour. What it throws goes to onError.
|
|
108
|
+
*/
|
|
109
|
+
domainChecks?: () => Promise<unknown>;
|
|
105
110
|
/** A pass that ran: counts only, never the tenant. Not awaited; what it throws is ignored. */
|
|
106
111
|
onPass?: (result: SlotlockMaintenanceResult) => void;
|
|
107
112
|
/**
|
package/maintenance.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ignoreRejection } from './hooks.js';
|
|
2
|
-
import { SLOTLOCK_MAX_WAITLIST_PER_TENANT, calendarEventRollingHorizon, } from './store.js';
|
|
2
|
+
import { SLOTLOCK_MAX_WAITLIST_PER_TENANT, buildSlotlockTenantExport, calendarEventRollingHorizon, } from './store.js';
|
|
3
3
|
import { readSlotlockHolidayCalendar } from './holiday-import.js';
|
|
4
4
|
/**
|
|
5
5
|
* One maintenance pass for a tenant: roll its recurring events to the horizon, then purge expired
|
|
@@ -73,6 +73,15 @@ export async function maintainSlotlockTenant(store, tenantRef, options = {}) {
|
|
|
73
73
|
options.signal?.throwIfAborted();
|
|
74
74
|
// Conversations expire 2 hours after their last message (dashboard.ts).
|
|
75
75
|
pruned += await withTenant((tenant) => tenant.pruneAssistantConversations({ tenantRef }));
|
|
76
|
+
// Audit events past retention (SLOTLOCK_AUDIT_RETENTION_DAYS, 180).
|
|
77
|
+
pruned += await withTenant((tenant) => tenant.pruneAuditEvents({ tenantRef }));
|
|
78
|
+
// Exports past their time go; one a stopped server left pending is built now (RFC 0004 D3).
|
|
79
|
+
const exports = await withTenant((tenant) => tenant.pruneTenantExports({ tenantRef }));
|
|
80
|
+
pruned += exports.deleted;
|
|
81
|
+
for (const exportId of exports.pending) {
|
|
82
|
+
options.signal?.throwIfAborted();
|
|
83
|
+
await buildSlotlockTenantExport(store, tenantRef, exportId);
|
|
84
|
+
}
|
|
76
85
|
const zones = await withTenant((tenant) => tenant.followZoneRules({ tenantRef, move: options.followZoneRules === true }));
|
|
77
86
|
const staleSources = await withTenant((tenant) => tenant.reportStaleCoverage({ tenantRef }));
|
|
78
87
|
// Approvals past their wait, and no-shows, first: the time they free is offered to the waitlist
|
|
@@ -293,6 +302,11 @@ export function startSlotlockMaintenance(options) {
|
|
|
293
302
|
return;
|
|
294
303
|
lastSweep = Date.now();
|
|
295
304
|
options.store.sweepAssistantConversations().catch(report);
|
|
305
|
+
// Team invitations finished a month ago, with the addresses they were sent to, and sessions
|
|
306
|
+
// ended a day ago.
|
|
307
|
+
options.store.sweepDashboardRecords().catch(report);
|
|
308
|
+
// Verified domains whose daily check (or a pending claim's hourly one) is due.
|
|
309
|
+
options.domainChecks?.().catch(report);
|
|
296
310
|
};
|
|
297
311
|
// Every tenant with data, once an interval: those no key has called since the server started too.
|
|
298
312
|
let lastDiscovery = Number.NEGATIVE_INFINITY;
|
package/oidc.d.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/** The algorithms Slotlock verifies: RSA PKCS#1 v1.5, RSA-PSS and ECDSA P-256, all with SHA-256. */
|
|
2
|
+
export declare const SLOTLOCK_OIDC_ALGORITHMS: readonly ["RS256", "PS256", "ES256"];
|
|
3
|
+
export type SlotlockOidcAlgorithm = (typeof SLOTLOCK_OIDC_ALGORITHMS)[number];
|
|
4
|
+
/** Why an ID token was refused (never shown with the token). */
|
|
5
|
+
export type SlotlockIdTokenRefusal = 'malformed' | 'algorithm' | 'key' | 'signature' | 'issuer' | 'audience' | 'expired' | 'not_yet_valid' | 'issued_at' | 'nonce' | 'subject';
|
|
6
|
+
/** RFC 4648 base64url, without padding, to bytes; null when it is not that. */
|
|
7
|
+
export declare function slotlockBase64UrlBytes(text: string): Uint8Array<ArrayBuffer> | null;
|
|
8
|
+
/** Bytes as RFC 4648 base64url, without padding. */
|
|
9
|
+
export declare function slotlockBase64Url(bytes: Uint8Array): string;
|
|
10
|
+
/** A JWK as an issuer publishes it. */
|
|
11
|
+
export interface SlotlockJwk {
|
|
12
|
+
kty?: unknown;
|
|
13
|
+
kid?: unknown;
|
|
14
|
+
use?: unknown;
|
|
15
|
+
alg?: unknown;
|
|
16
|
+
n?: unknown;
|
|
17
|
+
e?: unknown;
|
|
18
|
+
crv?: unknown;
|
|
19
|
+
x?: unknown;
|
|
20
|
+
y?: unknown;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A published key as a verification key for `alg`, or null when it does not fit it: RSA (at least
|
|
24
|
+
* 2048 bits) for RS256 and PS256, EC P-256 for ES256; `use`, when given, `sig`; `alg`, when given,
|
|
25
|
+
* the token's.
|
|
26
|
+
*/
|
|
27
|
+
export declare function importSlotlockJwk(jwk: SlotlockJwk, alg: SlotlockOidcAlgorithm): Promise<CryptoKey | null>;
|
|
28
|
+
/** Finds the key a token names: by issuer's JWKS and key id (see createSlotlockJwksCache). */
|
|
29
|
+
export type SlotlockKeyLookup = (kid: string | null, alg: SlotlockOidcAlgorithm) => Promise<CryptoKey | null>;
|
|
30
|
+
/** What an ID token is checked against. */
|
|
31
|
+
export interface SlotlockIdTokenExpectation {
|
|
32
|
+
/** The exact issuer, or the exact spellings one issuer documents (Google has two). */
|
|
33
|
+
issuers: readonly string[];
|
|
34
|
+
clientId: string;
|
|
35
|
+
/** The algorithms this issuer may sign with: a subset of SLOTLOCK_OIDC_ALGORITHMS. */
|
|
36
|
+
algorithms: readonly SlotlockOidcAlgorithm[];
|
|
37
|
+
keys: SlotlockKeyLookup;
|
|
38
|
+
nonce: string;
|
|
39
|
+
/** Milliseconds since the epoch. */
|
|
40
|
+
now: number;
|
|
41
|
+
}
|
|
42
|
+
/** An ID token checked: its claims, all of them, for the caller to read what it needs. */
|
|
43
|
+
export type SlotlockIdTokenResult = {
|
|
44
|
+
claims: Record<string, unknown>;
|
|
45
|
+
} | {
|
|
46
|
+
refusal: SlotlockIdTokenRefusal;
|
|
47
|
+
};
|
|
48
|
+
/** Check an ID token: its signature first, then its claims, in OpenID Connect Core's order. */
|
|
49
|
+
export declare function verifySlotlockIdToken(token: string, expected: SlotlockIdTokenExpectation): Promise<SlotlockIdTokenResult>;
|
|
50
|
+
/** Why a URL an Owner gave, or a document fetched from one, was refused. */
|
|
51
|
+
export declare class SlotlockOidcFetchError extends Error {
|
|
52
|
+
readonly code: 'url' | 'redirect' | 'status' | 'type' | 'size' | 'json' | 'network';
|
|
53
|
+
constructor(code: 'url' | 'redirect' | 'status' | 'type' | 'size' | 'json' | 'network', message: string);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Whether a URL is one Slotlock fetches for a team: https on the default port, a host name (never
|
|
57
|
+
* an IP literal), never localhost, a single label, or a private-use suffix; no credentials.
|
|
58
|
+
*/
|
|
59
|
+
export declare function slotlockOidcUrlAllowed(value: string): URL | null;
|
|
60
|
+
/**
|
|
61
|
+
* GET a JSON document from a URL an Owner influenced: refused unless slotlockOidcUrlAllowed; no
|
|
62
|
+
* redirect followed (any 3xx refuses); 5 seconds; 256 KB; a JSON content type. On Node, pass a
|
|
63
|
+
* fetch that refuses private addresses (webhooks.ts createSlotlockGuardedFetch).
|
|
64
|
+
*/
|
|
65
|
+
export declare function slotlockFetchOidcJson(value: string, options?: {
|
|
66
|
+
fetch?: typeof fetch;
|
|
67
|
+
timeoutMs?: number;
|
|
68
|
+
maxBytes?: number;
|
|
69
|
+
}): Promise<{
|
|
70
|
+
body: Record<string, unknown>;
|
|
71
|
+
maxAge: number | null;
|
|
72
|
+
}>;
|
|
73
|
+
/**
|
|
74
|
+
* A JSON object answered by a host an Owner chose, read to at most maxBytes (the declared length
|
|
75
|
+
* first, then as it streams), so a provider cannot make the server buffer an endless answer.
|
|
76
|
+
*/
|
|
77
|
+
export declare function slotlockReadJsonBody(response: Response, maxBytes: number): Promise<Record<string, unknown>>;
|
|
78
|
+
/** An issuer's endpoints, as its discovery document gave them when an Owner saved the connection. */
|
|
79
|
+
export interface SlotlockOidcProvider {
|
|
80
|
+
issuer: string;
|
|
81
|
+
authorizationEndpoint: string;
|
|
82
|
+
tokenEndpoint: string;
|
|
83
|
+
jwksUri: string;
|
|
84
|
+
algorithms: SlotlockOidcAlgorithm[];
|
|
85
|
+
/** RFC 9207: the issuer returns `iss` with the code, and Slotlock then checks it. */
|
|
86
|
+
issParameter: boolean;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* An issuer's discovery document (OpenID Connect Discovery 1.0): its `issuer` must be exactly the
|
|
90
|
+
* one entered, its endpoints https on public host names, and it must sign with an algorithm Slotlock
|
|
91
|
+
* verifies.
|
|
92
|
+
*/
|
|
93
|
+
export declare function discoverSlotlockOidc(issuer: string, options?: {
|
|
94
|
+
fetch?: typeof fetch;
|
|
95
|
+
}): Promise<SlotlockOidcProvider>;
|
|
96
|
+
/**
|
|
97
|
+
* An Entra ID tenant's v2.0 issuer, pinned to its GUID: never `common`, `organizations`,
|
|
98
|
+
* `consumers`, a domain name or the consumer tenant, so the multi-tenant `{tenantid}` issuer never
|
|
99
|
+
* exists in Slotlock. Null when the tenant is not one.
|
|
100
|
+
*/
|
|
101
|
+
export declare function slotlockEntraIssuer(tenant: string): string | null;
|
|
102
|
+
/** Google's issuer, in the two spellings it documents. */
|
|
103
|
+
export declare const SLOTLOCK_GOOGLE_ISSUERS: readonly string[];
|
|
104
|
+
/**
|
|
105
|
+
* Issuers' keys, by JWKS URL: kept for the answer's max-age (5 minutes to 24 hours). A key id not
|
|
106
|
+
* among them is fetched again at most once a minute per URL, one fetch at a time; while the URL
|
|
107
|
+
* fails, a set fetched within 24 hours still serves the keys it holds. At most `maxUrls` URLs and 20
|
|
108
|
+
* keys each are kept, only the fields verifying needs: the URLs are Owners' to choose.
|
|
109
|
+
*/
|
|
110
|
+
export declare function createSlotlockJwksCache(options: {
|
|
111
|
+
fetchJson?: (url: string) => Promise<{
|
|
112
|
+
body: Record<string, unknown>;
|
|
113
|
+
maxAge: number | null;
|
|
114
|
+
}>;
|
|
115
|
+
fetch?: typeof fetch;
|
|
116
|
+
now?: () => number;
|
|
117
|
+
/** How many JWKS URLs are kept (100): the least recently used goes first. */
|
|
118
|
+
maxUrls?: number;
|
|
119
|
+
}): {
|
|
120
|
+
/** The key `kid` names at `url` for `alg`, or null. */
|
|
121
|
+
key(url: string, kid: string | null, alg: SlotlockOidcAlgorithm): Promise<CryptoKey | null>;
|
|
122
|
+
/** A lookup bound to one JWKS URL, for verifySlotlockIdToken. */
|
|
123
|
+
lookup(url: string): SlotlockKeyLookup;
|
|
124
|
+
};
|
|
125
|
+
export type SlotlockJwksCache = ReturnType<typeof createSlotlockJwksCache>;
|
|
126
|
+
/**
|
|
127
|
+
* Seals and opens a team's client secret: AES-256-GCM under a key derived (HKDF-SHA-256) from
|
|
128
|
+
* SLOTLOCK_SSO_SECRET, a random 12-byte IV each time, and the team and connection as associated
|
|
129
|
+
* data, so a sealed secret moved to another row does not open.
|
|
130
|
+
*/
|
|
131
|
+
export declare function createSlotlockSecretBox(secret: string): {
|
|
132
|
+
seal(plaintext: string, associated: string): Promise<string>;
|
|
133
|
+
open(sealed: string, associated: string): Promise<string | null>;
|
|
134
|
+
};
|
|
135
|
+
export type SlotlockSecretBox = ReturnType<typeof createSlotlockSecretBox>;
|