@maravilla-labs/platform 0.1.34 → 0.1.36
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/dist/index.d.ts +357 -1
- package/dist/index.js +165 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +2 -2
- package/src/realtime.ts +24 -1
- package/src/remote-client.ts +175 -1
- package/src/types.ts +376 -0
package/dist/index.d.ts
CHANGED
|
@@ -598,6 +598,354 @@ interface PresenceService {
|
|
|
598
598
|
lastSeen?: number;
|
|
599
599
|
}>>;
|
|
600
600
|
}
|
|
601
|
+
/**
|
|
602
|
+
* Authenticated user record from the platform auth service.
|
|
603
|
+
*/
|
|
604
|
+
interface AuthUser {
|
|
605
|
+
/** Unique user ID (prefixed with "usr_") */
|
|
606
|
+
id: string;
|
|
607
|
+
/** User's email address */
|
|
608
|
+
email: string;
|
|
609
|
+
/** Whether the email has been verified */
|
|
610
|
+
email_verified: boolean;
|
|
611
|
+
/** Account status */
|
|
612
|
+
status: 'active' | 'suspended' | 'deactivated';
|
|
613
|
+
/** Authentication provider ("email", "google", "github", etc.) */
|
|
614
|
+
provider: string;
|
|
615
|
+
/** Group IDs the user belongs to */
|
|
616
|
+
groups: string[];
|
|
617
|
+
/** Unix timestamp when the user was created */
|
|
618
|
+
created_at: number;
|
|
619
|
+
/** Unix timestamp when the user was last updated */
|
|
620
|
+
updated_at: number;
|
|
621
|
+
/** Unix timestamp of last login (if any) */
|
|
622
|
+
last_login_at?: number;
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Snapshot of whoever is currently bound to the request as the caller.
|
|
626
|
+
*
|
|
627
|
+
* Populated by {@link AuthService.login} (implicit), {@link AuthService.setCurrentUser}
|
|
628
|
+
* (explicit), or left anonymous if neither has run for this request.
|
|
629
|
+
* This is exactly what per-resource policies see as `auth.*` when they run.
|
|
630
|
+
*/
|
|
631
|
+
interface AuthCaller {
|
|
632
|
+
/** Caller's user id, or `""` if anonymous */
|
|
633
|
+
user_id: string;
|
|
634
|
+
/** Caller's email, or `""` if anonymous */
|
|
635
|
+
email: string;
|
|
636
|
+
/** Admin flag from the session */
|
|
637
|
+
is_admin: boolean;
|
|
638
|
+
/** Role names (project-scoped) */
|
|
639
|
+
roles: string[];
|
|
640
|
+
/** `true` when no identity is bound to this request */
|
|
641
|
+
is_anonymous: boolean;
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Session returned after successful login or token refresh.
|
|
645
|
+
*/
|
|
646
|
+
interface AuthSession {
|
|
647
|
+
/** Short-lived JWT access token (default 15 min) */
|
|
648
|
+
access_token: string;
|
|
649
|
+
/** Single-use opaque refresh token (default 30 days) */
|
|
650
|
+
refresh_token: string;
|
|
651
|
+
/** Access token lifetime in seconds */
|
|
652
|
+
expires_in: number;
|
|
653
|
+
/** The authenticated user */
|
|
654
|
+
user: AuthUser;
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Custom registration field defined in project auth settings.
|
|
658
|
+
*/
|
|
659
|
+
interface AuthField {
|
|
660
|
+
/** Field key (used as form field name) */
|
|
661
|
+
key: string;
|
|
662
|
+
/** Display label */
|
|
663
|
+
label: string;
|
|
664
|
+
/** Field type: text, email, phone, date, number, select, boolean, url, textarea */
|
|
665
|
+
field_type: string;
|
|
666
|
+
/** Whether the field is required */
|
|
667
|
+
required: boolean;
|
|
668
|
+
/** Whether the field appears on the registration form */
|
|
669
|
+
show_on_register: boolean;
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Options for registering a new user.
|
|
673
|
+
*/
|
|
674
|
+
interface RegisterOptions {
|
|
675
|
+
/** User's email address */
|
|
676
|
+
email: string;
|
|
677
|
+
/** Password (minimum 8 characters) */
|
|
678
|
+
password: string;
|
|
679
|
+
/** Optional profile data (custom fields) */
|
|
680
|
+
profile?: Record<string, any>;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Options for logging in.
|
|
684
|
+
*/
|
|
685
|
+
interface LoginOptions {
|
|
686
|
+
/** User's email address */
|
|
687
|
+
email: string;
|
|
688
|
+
/** User's password */
|
|
689
|
+
password: string;
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Filter options for listing users.
|
|
693
|
+
*/
|
|
694
|
+
interface UserListFilter {
|
|
695
|
+
/** Max results per page (default 50) */
|
|
696
|
+
limit?: number;
|
|
697
|
+
/** Number of results to skip */
|
|
698
|
+
offset?: number;
|
|
699
|
+
/** Filter by account status */
|
|
700
|
+
status?: 'active' | 'suspended' | 'deactivated';
|
|
701
|
+
/** Filter by email (partial match) */
|
|
702
|
+
email_contains?: string;
|
|
703
|
+
/** Filter by group ID */
|
|
704
|
+
group_id?: string;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Paginated user list response.
|
|
708
|
+
*/
|
|
709
|
+
interface UserListResponse {
|
|
710
|
+
/** Users in this page */
|
|
711
|
+
users: AuthUser[];
|
|
712
|
+
/** Total number of matching users */
|
|
713
|
+
total: number;
|
|
714
|
+
/** Page size */
|
|
715
|
+
limit: number;
|
|
716
|
+
/** Offset */
|
|
717
|
+
offset: number;
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Options for updating a user.
|
|
721
|
+
*/
|
|
722
|
+
interface UpdateUserOptions {
|
|
723
|
+
/** New email address */
|
|
724
|
+
email?: string;
|
|
725
|
+
/** New status */
|
|
726
|
+
status?: 'active' | 'suspended' | 'deactivated';
|
|
727
|
+
/** Profile data to merge */
|
|
728
|
+
profile?: Record<string, any>;
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Auth service for end-user authentication and user management.
|
|
732
|
+
*
|
|
733
|
+
* @example
|
|
734
|
+
* ```typescript
|
|
735
|
+
* const platform = getPlatform();
|
|
736
|
+
*
|
|
737
|
+
* // Register a new user
|
|
738
|
+
* const user = await platform.auth.register({
|
|
739
|
+
* email: 'user@example.com',
|
|
740
|
+
* password: 'securePassword123'
|
|
741
|
+
* });
|
|
742
|
+
*
|
|
743
|
+
* // Login
|
|
744
|
+
* const session = await platform.auth.login({
|
|
745
|
+
* email: 'user@example.com',
|
|
746
|
+
* password: 'securePassword123'
|
|
747
|
+
* });
|
|
748
|
+
* // session.access_token — short-lived JWT
|
|
749
|
+
* // session.refresh_token — single-use refresh token
|
|
750
|
+
*
|
|
751
|
+
* // Validate a token (e.g. from Authorization header or cookie)
|
|
752
|
+
* const user = await platform.auth.validate(session.access_token);
|
|
753
|
+
*
|
|
754
|
+
* // Protect a route with withAuth middleware
|
|
755
|
+
* export default {
|
|
756
|
+
* fetch: platform.auth.withAuth(async (request) => {
|
|
757
|
+
* // request.user is guaranteed to be set
|
|
758
|
+
* return new Response(`Hello ${request.user.email}`);
|
|
759
|
+
* })
|
|
760
|
+
* };
|
|
761
|
+
* ```
|
|
762
|
+
*/
|
|
763
|
+
interface AuthService {
|
|
764
|
+
/**
|
|
765
|
+
* Register a new user with email and password.
|
|
766
|
+
* @returns The created user (not yet email-verified)
|
|
767
|
+
*/
|
|
768
|
+
register(options: RegisterOptions): Promise<AuthUser>;
|
|
769
|
+
/**
|
|
770
|
+
* Authenticate a user and create a session.
|
|
771
|
+
* @returns Session with access token, refresh token, and user info
|
|
772
|
+
*/
|
|
773
|
+
login(options: LoginOptions): Promise<AuthSession>;
|
|
774
|
+
/**
|
|
775
|
+
* Validate an access token and return the authenticated user.
|
|
776
|
+
* @param accessToken - JWT access token from login or refresh
|
|
777
|
+
* @throws If the token is invalid or expired
|
|
778
|
+
*/
|
|
779
|
+
validate(accessToken: string): Promise<AuthUser>;
|
|
780
|
+
/**
|
|
781
|
+
* Refresh a session using a refresh token (single-use).
|
|
782
|
+
* @param refreshToken - The refresh token from a previous login/refresh
|
|
783
|
+
* @returns New session with fresh access and refresh tokens
|
|
784
|
+
*/
|
|
785
|
+
refresh(refreshToken: string): Promise<AuthSession>;
|
|
786
|
+
/**
|
|
787
|
+
* Revoke a specific session.
|
|
788
|
+
*/
|
|
789
|
+
logout(sessionId: string): Promise<void>;
|
|
790
|
+
/**
|
|
791
|
+
* Get a user by ID.
|
|
792
|
+
* @returns The user, or null if not found
|
|
793
|
+
*/
|
|
794
|
+
getUser(userId: string): Promise<AuthUser | null>;
|
|
795
|
+
/**
|
|
796
|
+
* List users with optional filtering and pagination.
|
|
797
|
+
*/
|
|
798
|
+
listUsers(filter?: UserListFilter): Promise<UserListResponse>;
|
|
799
|
+
/**
|
|
800
|
+
* Update a user's email, status, or profile data.
|
|
801
|
+
*/
|
|
802
|
+
updateUser(userId: string, update: UpdateUserOptions): Promise<AuthUser>;
|
|
803
|
+
/**
|
|
804
|
+
* Delete a user and all their sessions.
|
|
805
|
+
*/
|
|
806
|
+
deleteUser(userId: string): Promise<void>;
|
|
807
|
+
/**
|
|
808
|
+
* Create an email verification token.
|
|
809
|
+
* @returns The verification token (caller decides how to deliver it)
|
|
810
|
+
*/
|
|
811
|
+
sendVerification(userId: string): Promise<{
|
|
812
|
+
token: string;
|
|
813
|
+
}>;
|
|
814
|
+
/**
|
|
815
|
+
* Verify an email address using a verification token.
|
|
816
|
+
*/
|
|
817
|
+
verifyEmail(token: string): Promise<void>;
|
|
818
|
+
/**
|
|
819
|
+
* Create a password reset token for an email address.
|
|
820
|
+
* @returns The reset token (caller decides how to deliver it)
|
|
821
|
+
*/
|
|
822
|
+
sendPasswordReset(email: string): Promise<{
|
|
823
|
+
token: string;
|
|
824
|
+
}>;
|
|
825
|
+
/**
|
|
826
|
+
* Reset a password using a reset token.
|
|
827
|
+
*/
|
|
828
|
+
resetPassword(token: string, newPassword: string): Promise<void>;
|
|
829
|
+
/**
|
|
830
|
+
* Change a user's password (requires old password).
|
|
831
|
+
*/
|
|
832
|
+
changePassword(userId: string, oldPassword: string, newPassword: string): Promise<void>;
|
|
833
|
+
/**
|
|
834
|
+
* Get the configured registration fields for this project.
|
|
835
|
+
*/
|
|
836
|
+
getFieldConfig(): Promise<{
|
|
837
|
+
fields: AuthField[];
|
|
838
|
+
}>;
|
|
839
|
+
/**
|
|
840
|
+
* Start an OAuth flow by generating an authorization URL.
|
|
841
|
+
* Redirect the user to the returned URL to begin authentication.
|
|
842
|
+
*
|
|
843
|
+
* @param provider - Provider name: "google", "github", "okta", or "custom_oidc"
|
|
844
|
+
* @param options - Optional configuration
|
|
845
|
+
* @returns Object with `auth_url` (redirect target) and `state` (for CSRF verification)
|
|
846
|
+
*/
|
|
847
|
+
getOAuthUrl(provider: string, options?: {
|
|
848
|
+
redirectUri?: string;
|
|
849
|
+
}): Promise<{
|
|
850
|
+
auth_url: string;
|
|
851
|
+
state: string;
|
|
852
|
+
}>;
|
|
853
|
+
/**
|
|
854
|
+
* Complete an OAuth flow by exchanging the authorization code.
|
|
855
|
+
* Call this after the provider redirects back with a code and state.
|
|
856
|
+
*
|
|
857
|
+
* @param provider - Provider name
|
|
858
|
+
* @param params - The code and state from the OAuth callback
|
|
859
|
+
* @returns Either a session (user authenticated) or a link_required result
|
|
860
|
+
*/
|
|
861
|
+
handleOAuthCallback(provider: string, params: {
|
|
862
|
+
code: string;
|
|
863
|
+
state: string;
|
|
864
|
+
}): Promise<AuthSession | {
|
|
865
|
+
type: 'LinkRequired';
|
|
866
|
+
email: string;
|
|
867
|
+
provider: string;
|
|
868
|
+
provider_id: string;
|
|
869
|
+
existing_user_id: string;
|
|
870
|
+
}>;
|
|
871
|
+
/**
|
|
872
|
+
* Middleware helper that validates auth and injects `request.user`.
|
|
873
|
+
* Returns 401 JSON response if no valid token is found.
|
|
874
|
+
*
|
|
875
|
+
* Extracts token from `Authorization: Bearer <token>` header
|
|
876
|
+
* or `__session` cookie.
|
|
877
|
+
*
|
|
878
|
+
* @example
|
|
879
|
+
* ```typescript
|
|
880
|
+
* export default {
|
|
881
|
+
* fetch: platform.auth.withAuth(async (request) => {
|
|
882
|
+
* const data = await platform.db.items.find({ owner: request.user.id });
|
|
883
|
+
* return Response.json(data);
|
|
884
|
+
* })
|
|
885
|
+
* };
|
|
886
|
+
* ```
|
|
887
|
+
*/
|
|
888
|
+
withAuth<T extends (request: Request & {
|
|
889
|
+
user: AuthUser;
|
|
890
|
+
}) => Promise<Response>>(handler: T): (request: Request) => Promise<Response>;
|
|
891
|
+
/**
|
|
892
|
+
* Explicitly bind the caller for the remainder of this request.
|
|
893
|
+
* Pass a JWT to validate + bind, or `null` / `""` to clear.
|
|
894
|
+
*
|
|
895
|
+
* `login()` already binds implicitly on success; reach for `setCurrentUser`
|
|
896
|
+
* when you receive a JWT from an inbound `Authorization` header or cookie
|
|
897
|
+
* and want subsequent KV/DB/realtime/media ops to run as that user.
|
|
898
|
+
*
|
|
899
|
+
* Not available on remote clients — throws.
|
|
900
|
+
*/
|
|
901
|
+
setCurrentUser(token: string | null): Promise<void>;
|
|
902
|
+
/**
|
|
903
|
+
* Snapshot of the currently bound caller. Returns an anonymous caller
|
|
904
|
+
* (`is_anonymous: true`) when no identity has been bound.
|
|
905
|
+
*
|
|
906
|
+
* Not available on remote clients — throws.
|
|
907
|
+
*/
|
|
908
|
+
getCurrentUser(): AuthCaller;
|
|
909
|
+
/**
|
|
910
|
+
* Ask the policy engine whether the bound caller would be allowed to
|
|
911
|
+
* perform `action` on `resourceId`, given the supplied `node` payload.
|
|
912
|
+
* Returns a boolean — never throws on denial.
|
|
913
|
+
*
|
|
914
|
+
* The check runs the exact same evaluator that gates direct KV/DB/
|
|
915
|
+
* realtime/media ops, so `can(...)` is authoritative.
|
|
916
|
+
*
|
|
917
|
+
* @example
|
|
918
|
+
* ```typescript
|
|
919
|
+
* const ok = await platform.auth.can("delete", "documents", {
|
|
920
|
+
* owner: doc.owner,
|
|
921
|
+
* status: doc.status,
|
|
922
|
+
* });
|
|
923
|
+
* if (!ok) return new Response("Forbidden", { status: 403 });
|
|
924
|
+
* ```
|
|
925
|
+
*
|
|
926
|
+
* Not available on remote clients — throws.
|
|
927
|
+
*/
|
|
928
|
+
can(action: string, resourceId: string, node?: Record<string, unknown> | null): Promise<boolean>;
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Per-request opt-out toggle for the Layer 2 policy evaluator.
|
|
932
|
+
*
|
|
933
|
+
* Flipping `setEnabled(false)` disables **per-resource policies only** for
|
|
934
|
+
* the remainder of the current request. Layer 1 (tenant + owner isolation)
|
|
935
|
+
* is always enforced — no call can ever escape its tenant. Every flip is
|
|
936
|
+
* audit-logged server-side with the caller's identity.
|
|
937
|
+
*
|
|
938
|
+
* Intended for trusted in-app flows (first-run seeders, admin jobs). Do not
|
|
939
|
+
* toggle based on untrusted input.
|
|
940
|
+
*
|
|
941
|
+
* Not available on remote clients — throws.
|
|
942
|
+
*/
|
|
943
|
+
interface PolicyService {
|
|
944
|
+
/** Disable or re-enable Layer 2 policy checks for this request. */
|
|
945
|
+
setEnabled(enabled: boolean): void;
|
|
946
|
+
/** `true` when Layer 2 is active for this request. */
|
|
947
|
+
isEnabled(): boolean;
|
|
948
|
+
}
|
|
601
949
|
interface Platform {
|
|
602
950
|
/** Environment containing all available platform services */
|
|
603
951
|
env: PlatformEnv;
|
|
@@ -605,6 +953,10 @@ interface Platform {
|
|
|
605
953
|
media?: MediaService;
|
|
606
954
|
/** Realtime service for pub/sub channels and presence */
|
|
607
955
|
realtime: RealtimeService;
|
|
956
|
+
/** Auth service for end-user authentication, identity binding, and authorization checks */
|
|
957
|
+
auth: AuthService;
|
|
958
|
+
/** Per-request Layer 2 policy toggle (Layer 1 isolation always applies) */
|
|
959
|
+
policy: PolicyService;
|
|
608
960
|
}
|
|
609
961
|
|
|
610
962
|
interface RenEvent {
|
|
@@ -721,10 +1073,14 @@ declare class RealtimeClient {
|
|
|
721
1073
|
presence(channel: string): {
|
|
722
1074
|
/** Join the channel with presence (auto-rejoins on reconnect) */
|
|
723
1075
|
join: (userId: string, metadata?: any) => void;
|
|
1076
|
+
/** Update metadata for the current presence (must have joined first) */
|
|
1077
|
+
update: (metadata?: any) => void;
|
|
724
1078
|
/** Leave the channel */
|
|
725
1079
|
leave: () => void;
|
|
726
1080
|
/** Listen for users joining */
|
|
727
1081
|
onJoin: (callback: (member: PresenceMember) => void) => Unsubscribe;
|
|
1082
|
+
/** Listen for metadata updates */
|
|
1083
|
+
onUpdate: (callback: (member: PresenceMember) => void) => Unsubscribe;
|
|
728
1084
|
/** Listen for users leaving */
|
|
729
1085
|
onLeave: (callback: (member: PresenceMember) => void) => Unsubscribe;
|
|
730
1086
|
};
|
|
@@ -963,4 +1319,4 @@ declare function getPlatform(options?: {
|
|
|
963
1319
|
*/
|
|
964
1320
|
declare function clearPlatformCache(): void;
|
|
965
1321
|
|
|
966
|
-
export { type Database, type DbFindOptions, type KvListResult, type KvNamespace, MediaLocalParticipant, type MediaParticipant, type MediaParticipantInfo, MediaRoom, MediaRoomEvent, type MediaRoomInfo, type MediaRoomInfoSettings, type MediaRoomOptions, type MediaService, type MediaTokenResult, type MediaTrackPublication, type Platform, type PlatformEnv, type PresenceMember, type PresenceService, RealtimeClient, type RealtimeClientOptions, type RealtimeEvent, type RealtimeService, RemoteMediaService, RenClient, type RenClientOptions, type RenEvent, type Storage$1 as Storage, type StoragePutStreamSource, type TrackKind, type TrackSource, type VideoResolution, attachTrack, clearPlatformCache, detachTrack, getOrCreateClientId, getPlatform, renFetch, storageDelete, storageUpload };
|
|
1322
|
+
export { type AuthCaller, type AuthField, type AuthService, type AuthSession, type AuthUser, type Database, type DbFindOptions, type KvListResult, type KvNamespace, type LoginOptions, MediaLocalParticipant, type MediaParticipant, type MediaParticipantInfo, MediaRoom, MediaRoomEvent, type MediaRoomInfo, type MediaRoomInfoSettings, type MediaRoomOptions, type MediaService, type MediaTokenResult, type MediaTrackPublication, type Platform, type PlatformEnv, type PolicyService, type PresenceMember, type PresenceService, RealtimeClient, type RealtimeClientOptions, type RealtimeEvent, type RealtimeService, type RegisterOptions, RemoteMediaService, RenClient, type RenClientOptions, type RenEvent, type Storage$1 as Storage, type StoragePutStreamSource, type TrackKind, type TrackSource, type UpdateUserOptions, type UserListFilter, type UserListResponse, type VideoResolution, attachTrack, clearPlatformCache, detachTrack, getOrCreateClientId, getPlatform, renFetch, storageDelete, storageUpload };
|
package/dist/index.js
CHANGED
|
@@ -380,6 +380,140 @@ var RemoteRealtimeService = class {
|
|
|
380
380
|
return data.channels ?? [];
|
|
381
381
|
}
|
|
382
382
|
};
|
|
383
|
+
var RemoteAuthService = class {
|
|
384
|
+
constructor(baseUrl, headers) {
|
|
385
|
+
this.baseUrl = baseUrl;
|
|
386
|
+
this.headers = headers;
|
|
387
|
+
}
|
|
388
|
+
baseUrl;
|
|
389
|
+
headers;
|
|
390
|
+
async post(path, body) {
|
|
391
|
+
const res = await fetch(`${this.baseUrl}/api/platform/auth${path}`, {
|
|
392
|
+
method: "POST",
|
|
393
|
+
headers: this.headers,
|
|
394
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
395
|
+
});
|
|
396
|
+
if (!res.ok) {
|
|
397
|
+
const text = await res.text();
|
|
398
|
+
throw new Error(`Auth error (${res.status}): ${text}`);
|
|
399
|
+
}
|
|
400
|
+
if (res.status === 204) return void 0;
|
|
401
|
+
return res.json();
|
|
402
|
+
}
|
|
403
|
+
async register(options) {
|
|
404
|
+
return this.post("/register", options);
|
|
405
|
+
}
|
|
406
|
+
async login(options) {
|
|
407
|
+
return this.post("/login", options);
|
|
408
|
+
}
|
|
409
|
+
async validate(accessToken) {
|
|
410
|
+
return this.post("/validate", { token: accessToken });
|
|
411
|
+
}
|
|
412
|
+
async refresh(refreshToken) {
|
|
413
|
+
return this.post("/refresh", { token: refreshToken });
|
|
414
|
+
}
|
|
415
|
+
async logout(sessionId) {
|
|
416
|
+
await this.post("/logout", { session_id: sessionId });
|
|
417
|
+
}
|
|
418
|
+
async getUser(userId) {
|
|
419
|
+
return this.post("/get-user", { user_id: userId });
|
|
420
|
+
}
|
|
421
|
+
async listUsers(filter) {
|
|
422
|
+
return this.post("/list-users", filter ?? {});
|
|
423
|
+
}
|
|
424
|
+
async updateUser(userId, update) {
|
|
425
|
+
return this.post("/update-user", { user_id: userId, ...update });
|
|
426
|
+
}
|
|
427
|
+
async deleteUser(userId) {
|
|
428
|
+
await this.post("/delete-user", { user_id: userId });
|
|
429
|
+
}
|
|
430
|
+
async sendVerification(userId) {
|
|
431
|
+
return this.post("/send-verification", { user_id: userId });
|
|
432
|
+
}
|
|
433
|
+
async verifyEmail(token) {
|
|
434
|
+
await this.post("/verify-email", { token });
|
|
435
|
+
}
|
|
436
|
+
async sendPasswordReset(email) {
|
|
437
|
+
return this.post("/send-password-reset", { email });
|
|
438
|
+
}
|
|
439
|
+
async resetPassword(token, newPassword) {
|
|
440
|
+
await this.post("/reset-password", { token, new_password: newPassword });
|
|
441
|
+
}
|
|
442
|
+
async changePassword(userId, oldPassword, newPassword) {
|
|
443
|
+
await this.post("/change-password", { user_id: userId, old_password: oldPassword, new_password: newPassword });
|
|
444
|
+
}
|
|
445
|
+
async getFieldConfig() {
|
|
446
|
+
return this.post("/field-config");
|
|
447
|
+
}
|
|
448
|
+
async getOAuthUrl(provider, options) {
|
|
449
|
+
return this.post("/oauth/start", { provider, redirect_uri: options?.redirectUri || `/_auth/callback/${provider}` });
|
|
450
|
+
}
|
|
451
|
+
async handleOAuthCallback(provider, params) {
|
|
452
|
+
return this.post("/oauth/callback", { provider, code: params.code, state: params.state });
|
|
453
|
+
}
|
|
454
|
+
withAuth(handler) {
|
|
455
|
+
const self = this;
|
|
456
|
+
return async function(request) {
|
|
457
|
+
let token = null;
|
|
458
|
+
const authHeader = request.headers.get("Authorization");
|
|
459
|
+
if (authHeader?.startsWith("Bearer ")) {
|
|
460
|
+
token = authHeader.slice(7);
|
|
461
|
+
} else {
|
|
462
|
+
const cookies = request.headers.get("Cookie") ?? "";
|
|
463
|
+
const match = cookies.match(/__session=([^;]+)/);
|
|
464
|
+
if (match) token = match[1];
|
|
465
|
+
}
|
|
466
|
+
if (!token) {
|
|
467
|
+
return new Response(JSON.stringify({ error: "Authentication required" }), {
|
|
468
|
+
status: 401,
|
|
469
|
+
headers: { "Content-Type": "application/json" }
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
try {
|
|
473
|
+
const user = await self.validate(token);
|
|
474
|
+
request.user = user;
|
|
475
|
+
return handler(request);
|
|
476
|
+
} catch {
|
|
477
|
+
return new Response(JSON.stringify({ error: "Invalid or expired token" }), {
|
|
478
|
+
status: 401,
|
|
479
|
+
headers: { "Content-Type": "application/json" }
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
// ── Request-scoped identity + authorization ──
|
|
485
|
+
//
|
|
486
|
+
// These APIs operate on per-request state inside the platform runtime and
|
|
487
|
+
// don't have a remote equivalent. Throw so callers get a clear message
|
|
488
|
+
// instead of silently wrong behavior.
|
|
489
|
+
setCurrentUser(_token) {
|
|
490
|
+
return Promise.reject(new Error(
|
|
491
|
+
"platform.auth.setCurrentUser is only available inside the Maravilla runtime. Remote clients should pass the Authorization header with each request instead."
|
|
492
|
+
));
|
|
493
|
+
}
|
|
494
|
+
getCurrentUser() {
|
|
495
|
+
throw new Error(
|
|
496
|
+
"platform.auth.getCurrentUser is only available inside the Maravilla runtime. Remote clients have no per-request caller context."
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
can(_action, _resourceId, _node) {
|
|
500
|
+
return Promise.reject(new Error(
|
|
501
|
+
"platform.auth.can is only available inside the Maravilla runtime. Remote clients cannot evaluate per-request policies because there is no bound caller."
|
|
502
|
+
));
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
var RemotePolicyService = class {
|
|
506
|
+
setEnabled(_enabled) {
|
|
507
|
+
throw new Error(
|
|
508
|
+
"platform.policy.setEnabled is only available inside the Maravilla runtime."
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
isEnabled() {
|
|
512
|
+
throw new Error(
|
|
513
|
+
"platform.policy.isEnabled is only available inside the Maravilla runtime."
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
};
|
|
383
517
|
function createRemoteClient(baseUrl, tenant) {
|
|
384
518
|
const headers = {
|
|
385
519
|
"Content-Type": "application/json",
|
|
@@ -394,6 +528,8 @@ function createRemoteClient(baseUrl, tenant) {
|
|
|
394
528
|
const storage = new RemoteStorage(baseUrl, headers);
|
|
395
529
|
const media = new RemoteMediaService(baseUrl, headers);
|
|
396
530
|
const realtime = new RemoteRealtimeService(baseUrl, headers);
|
|
531
|
+
const auth = new RemoteAuthService(baseUrl, headers);
|
|
532
|
+
const policy = new RemotePolicyService();
|
|
397
533
|
return {
|
|
398
534
|
env: {
|
|
399
535
|
KV: kvProxy,
|
|
@@ -401,7 +537,9 @@ function createRemoteClient(baseUrl, tenant) {
|
|
|
401
537
|
STORAGE: storage
|
|
402
538
|
},
|
|
403
539
|
media,
|
|
404
|
-
realtime
|
|
540
|
+
realtime,
|
|
541
|
+
auth,
|
|
542
|
+
policy
|
|
405
543
|
};
|
|
406
544
|
}
|
|
407
545
|
|
|
@@ -677,7 +815,7 @@ var RealtimeClient = class {
|
|
|
677
815
|
listeners.forEach((cb) => cb(event));
|
|
678
816
|
}
|
|
679
817
|
}
|
|
680
|
-
if (event.event === "presence:join" || event.event === "presence:leave") {
|
|
818
|
+
if (event.event === "presence:join" || event.event === "presence:leave" || event.event === "presence:update") {
|
|
681
819
|
const presenceSet = this.presenceListeners.get(event.channel);
|
|
682
820
|
if (presenceSet) {
|
|
683
821
|
const member = {
|
|
@@ -687,6 +825,8 @@ var RealtimeClient = class {
|
|
|
687
825
|
};
|
|
688
826
|
if (event.event === "presence:join") {
|
|
689
827
|
presenceSet.onJoin.forEach((cb) => cb(member));
|
|
828
|
+
} else if (event.event === "presence:update") {
|
|
829
|
+
presenceSet.onUpdate.forEach((cb) => cb(member));
|
|
690
830
|
} else {
|
|
691
831
|
presenceSet.onLeave.forEach((cb) => cb(member));
|
|
692
832
|
}
|
|
@@ -740,7 +880,8 @@ var RealtimeClient = class {
|
|
|
740
880
|
if (!this.presenceListeners.has(channel)) {
|
|
741
881
|
this.presenceListeners.set(channel, {
|
|
742
882
|
onJoin: /* @__PURE__ */ new Set(),
|
|
743
|
-
onLeave: /* @__PURE__ */ new Set()
|
|
883
|
+
onLeave: /* @__PURE__ */ new Set(),
|
|
884
|
+
onUpdate: /* @__PURE__ */ new Set()
|
|
744
885
|
});
|
|
745
886
|
}
|
|
746
887
|
const listeners = this.presenceListeners.get(channel);
|
|
@@ -755,6 +896,18 @@ var RealtimeClient = class {
|
|
|
755
896
|
metadata
|
|
756
897
|
});
|
|
757
898
|
},
|
|
899
|
+
/** Update metadata for the current presence (must have joined first) */
|
|
900
|
+
update: (metadata) => {
|
|
901
|
+
const info = this.joinedPresence.get(channel);
|
|
902
|
+
if (!info) return;
|
|
903
|
+
info.metadata = metadata;
|
|
904
|
+
this.sendRaw({
|
|
905
|
+
action: "presence:update",
|
|
906
|
+
channel,
|
|
907
|
+
userId: info.userId,
|
|
908
|
+
metadata
|
|
909
|
+
});
|
|
910
|
+
},
|
|
758
911
|
/** Leave the channel */
|
|
759
912
|
leave: () => {
|
|
760
913
|
this.joinedPresence.delete(channel);
|
|
@@ -767,6 +920,13 @@ var RealtimeClient = class {
|
|
|
767
920
|
listeners.onJoin.delete(callback);
|
|
768
921
|
};
|
|
769
922
|
},
|
|
923
|
+
/** Listen for metadata updates */
|
|
924
|
+
onUpdate: (callback) => {
|
|
925
|
+
listeners.onUpdate.add(callback);
|
|
926
|
+
return () => {
|
|
927
|
+
listeners.onUpdate.delete(callback);
|
|
928
|
+
};
|
|
929
|
+
},
|
|
770
930
|
/** Listen for users leaving */
|
|
771
931
|
onLeave: (callback) => {
|
|
772
932
|
listeners.onLeave.add(callback);
|
|
@@ -1048,7 +1208,7 @@ var MediaRoom = class _MediaRoom {
|
|
|
1048
1208
|
};
|
|
1049
1209
|
|
|
1050
1210
|
// src/index.ts
|
|
1051
|
-
var cachedPlatform =
|
|
1211
|
+
var cachedPlatform = void 0;
|
|
1052
1212
|
function getPlatform(options) {
|
|
1053
1213
|
if (cachedPlatform) {
|
|
1054
1214
|
return cachedPlatform;
|
|
@@ -1075,7 +1235,7 @@ function getPlatform(options) {
|
|
|
1075
1235
|
return cachedPlatform;
|
|
1076
1236
|
}
|
|
1077
1237
|
function clearPlatformCache() {
|
|
1078
|
-
cachedPlatform =
|
|
1238
|
+
cachedPlatform = void 0;
|
|
1079
1239
|
if (typeof globalThis !== "undefined") {
|
|
1080
1240
|
globalThis.__maravilla_platform = void 0;
|
|
1081
1241
|
}
|