@progalaxyelabs/ngx-stonescriptphp-client 1.17.1 → 1.18.1

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.
@@ -602,6 +602,289 @@ function provideNgxStoneScriptPhpClient(environment, plugin) {
602
602
  ]);
603
603
  }
604
604
 
605
+ /**
606
+ * Auth plugin for progalaxyelabs-auth (Rust/Axum).
607
+ *
608
+ * Speaks the Rust auth server's native format:
609
+ * - Login: { access_token, refresh_token, identity, membership, ... }
610
+ * - Tenant selection: { requires_tenant_selection, selection_token, memberships }
611
+ * - New identity: { access_token, identity, is_new_identity, memberships:[] }
612
+ * - select-tenant: Bearer header + { tenant_id } body
613
+ * - refresh: { access_token, refresh_token } body mode
614
+ */
615
+ class ProgalaxyElabsAuth {
616
+ config;
617
+ constructor(config) {
618
+ this.config = config;
619
+ }
620
+ get host() {
621
+ return this.config.host;
622
+ }
623
+ // -- Login ----------------------------------------------------------------
624
+ async login(email, password) {
625
+ try {
626
+ const response = await fetch(`${this.host}/api/auth/login`, {
627
+ method: 'POST',
628
+ headers: { 'Content-Type': 'application/json' },
629
+ body: JSON.stringify({ email, password, platform: this.config.platformCode })
630
+ });
631
+ const data = await response.json();
632
+ if (!response.ok) {
633
+ return { success: false, message: data.error || data.message || 'Login failed' };
634
+ }
635
+ return this.handleLoginResponse(data);
636
+ }
637
+ catch {
638
+ return { success: false, message: 'Network error. Please try again.' };
639
+ }
640
+ }
641
+ async register(email, password, displayName) {
642
+ try {
643
+ const response = await fetch(`${this.host}/api/auth/register`, {
644
+ method: 'POST',
645
+ headers: { 'Content-Type': 'application/json' },
646
+ body: JSON.stringify({
647
+ email,
648
+ password,
649
+ display_name: displayName,
650
+ platform: this.config.platformCode
651
+ })
652
+ });
653
+ const data = await response.json();
654
+ if (!response.ok) {
655
+ return { success: false, message: data.error || data.message || 'Registration failed' };
656
+ }
657
+ return this.handleLoginResponse(data);
658
+ }
659
+ catch {
660
+ return { success: false, message: 'Network error. Please try again.' };
661
+ }
662
+ }
663
+ // -- Logout ---------------------------------------------------------------
664
+ async logout(refreshToken) {
665
+ try {
666
+ await fetch(`${this.host}/api/auth/logout`, {
667
+ method: 'POST',
668
+ headers: { 'Content-Type': 'application/json' },
669
+ body: JSON.stringify({ refresh_token: refreshToken })
670
+ });
671
+ }
672
+ catch { /* ignore */ }
673
+ }
674
+ // -- Session & Refresh ----------------------------------------------------
675
+ async checkSession() {
676
+ return { success: false };
677
+ }
678
+ async refresh(accessToken, refreshToken) {
679
+ if (!refreshToken)
680
+ return null;
681
+ try {
682
+ const response = await fetch(`${this.host}/api/auth/refresh`, {
683
+ method: 'POST',
684
+ headers: { 'Content-Type': 'application/json' },
685
+ body: JSON.stringify({ access_token: accessToken, refresh_token: refreshToken })
686
+ });
687
+ if (!response.ok)
688
+ return null;
689
+ const data = await response.json();
690
+ // Refresh can also return tenant_selection if memberships changed
691
+ if (data.requires_tenant_selection)
692
+ return null;
693
+ return data.access_token ?? null;
694
+ }
695
+ catch {
696
+ return null;
697
+ }
698
+ }
699
+ // -- Tenant operations ----------------------------------------------------
700
+ async selectTenant(tenantId, selectionToken) {
701
+ try {
702
+ const response = await fetch(`${this.host}/api/auth/select-tenant`, {
703
+ method: 'POST',
704
+ headers: {
705
+ 'Authorization': `Bearer ${selectionToken}`,
706
+ 'Content-Type': 'application/json'
707
+ },
708
+ body: JSON.stringify({ tenant_id: tenantId })
709
+ });
710
+ const data = await response.json();
711
+ if (!response.ok) {
712
+ return { success: false, message: data.error || data.message || 'Tenant selection failed' };
713
+ }
714
+ return {
715
+ success: true,
716
+ accessToken: data.access_token,
717
+ refreshToken: data.refresh_token,
718
+ user: this.toUser(data.identity),
719
+ membership: this.toMembership(data.membership),
720
+ };
721
+ }
722
+ catch {
723
+ return { success: false, message: 'Network error. Please try again.' };
724
+ }
725
+ }
726
+ async getTenantMemberships(accessToken) {
727
+ try {
728
+ const platformCode = encodeURIComponent(this.config.platformCode);
729
+ const response = await fetch(`${this.host}/api/auth/memberships?platform_code=${platformCode}`, {
730
+ method: 'GET',
731
+ headers: { 'Authorization': `Bearer ${accessToken}` }
732
+ });
733
+ if (!response.ok)
734
+ return [];
735
+ const data = await response.json();
736
+ return (data.memberships || []).map((m) => this.toMembership(m));
737
+ }
738
+ catch {
739
+ return [];
740
+ }
741
+ }
742
+ async checkTenantSlugAvailable(slug) {
743
+ try {
744
+ const response = await fetch(`${this.host}/api/auth/check-tenant-slug/${slug}`);
745
+ const data = await response.json();
746
+ return { available: data.available || false, suggestion: data.suggestion };
747
+ }
748
+ catch {
749
+ return { available: true };
750
+ }
751
+ }
752
+ async checkOnboardingStatus(identityId, platformCode) {
753
+ const platform = platformCode ?? this.config.platformCode;
754
+ const response = await fetch(`${this.host}/api/auth/onboarding/status?platform_code=${platform}&identity_id=${identityId}`);
755
+ if (!response.ok)
756
+ throw new Error('Failed to check onboarding status');
757
+ return response.json();
758
+ }
759
+ async checkEmail(email) {
760
+ try {
761
+ const response = await fetch(`${this.host}/api/auth/check-email`, {
762
+ method: 'POST',
763
+ headers: { 'Content-Type': 'application/json' },
764
+ body: JSON.stringify({ email })
765
+ });
766
+ const data = await response.json();
767
+ return { exists: data.exists, user: data.user };
768
+ }
769
+ catch {
770
+ return { exists: false };
771
+ }
772
+ }
773
+ // -- OAuth ----------------------------------------------------------------
774
+ async loginWithProvider(provider) {
775
+ return new Promise((resolve) => {
776
+ const width = 500, height = 600;
777
+ const left = (window.screen.width - width) / 2;
778
+ const top = (window.screen.height - height) / 2;
779
+ const oauthUrl = `${this.host}/oauth/${provider}?platform=${this.config.platformCode}&mode=popup`;
780
+ const popup = window.open(oauthUrl, `${provider}_login`, `width=${width},height=${height},left=${left},top=${top}`);
781
+ if (!popup) {
782
+ resolve({ success: false, message: 'Popup blocked. Please allow popups for this site.' });
783
+ return;
784
+ }
785
+ const cleanup = () => {
786
+ window.removeEventListener('message', messageHandler);
787
+ clearInterval(checkClosed);
788
+ if (popup && !popup.closed)
789
+ popup.close();
790
+ };
791
+ const messageHandler = (event) => {
792
+ if (event.origin !== new URL(this.host).origin)
793
+ return;
794
+ cleanup();
795
+ if (event.data.type === 'oauth_success') {
796
+ resolve({
797
+ success: true,
798
+ accessToken: event.data.access_token,
799
+ refreshToken: event.data.refresh_token,
800
+ user: this.toUser(event.data.user || event.data.identity),
801
+ membership: event.data.membership ? this.toMembership(event.data.membership) : undefined,
802
+ });
803
+ }
804
+ else if (event.data.type === 'oauth_tenant_selection') {
805
+ resolve({
806
+ success: true,
807
+ accessToken: event.data.selection_token,
808
+ memberships: (event.data.memberships || []).map((m) => this.toMembership(m)),
809
+ });
810
+ }
811
+ else if (event.data.type === 'oauth_new_identity') {
812
+ resolve({
813
+ success: true,
814
+ accessToken: event.data.access_token,
815
+ refreshToken: event.data.refresh_token,
816
+ isNewIdentity: true,
817
+ authMethod: event.data.auth_method,
818
+ oauthProvider: event.data.oauth_provider,
819
+ identity: event.data.identity,
820
+ });
821
+ }
822
+ else if (event.data.type === 'oauth_error') {
823
+ resolve({ success: false, message: event.data.message || 'OAuth login failed' });
824
+ }
825
+ };
826
+ window.addEventListener('message', messageHandler);
827
+ const checkClosed = setInterval(() => {
828
+ if (popup.closed) {
829
+ clearInterval(checkClosed);
830
+ window.removeEventListener('message', messageHandler);
831
+ resolve({ success: false, message: 'Login cancelled' });
832
+ }
833
+ }, 500);
834
+ });
835
+ }
836
+ // -- Internal helpers -----------------------------------------------------
837
+ handleLoginResponse(data) {
838
+ // New identity — needs onboarding
839
+ if (data.is_new_identity) {
840
+ return {
841
+ success: true,
842
+ accessToken: data.access_token,
843
+ refreshToken: data.refresh_token,
844
+ isNewIdentity: true,
845
+ authMethod: data.auth_method,
846
+ oauthProvider: data.oauth_provider,
847
+ identity: data.identity,
848
+ };
849
+ }
850
+ // Multi-tenant selection required
851
+ if (data.requires_tenant_selection) {
852
+ return {
853
+ success: true,
854
+ accessToken: data.selection_token,
855
+ memberships: (data.memberships || []).map((m) => this.toMembership(m)),
856
+ };
857
+ }
858
+ // Standard success (single tenant auto-selected or tenant specified)
859
+ return {
860
+ success: true,
861
+ accessToken: data.access_token,
862
+ refreshToken: data.refresh_token,
863
+ user: this.toUser(data.identity),
864
+ membership: this.toMembership(data.membership),
865
+ };
866
+ }
867
+ toUser(raw) {
868
+ if (!raw)
869
+ return undefined;
870
+ return {
871
+ email: raw.email,
872
+ display_name: raw.display_name ?? raw.email?.split('@')[0] ?? '',
873
+ photo_url: raw.photo_url ?? raw.picture,
874
+ is_email_verified: raw.is_email_verified ?? false
875
+ };
876
+ }
877
+ toMembership(raw) {
878
+ return {
879
+ tenant_id: raw.tenant_id,
880
+ slug: raw.tenant_slug ?? raw.slug ?? '',
881
+ name: raw.tenant_name ?? raw.name ?? '',
882
+ role: raw.role ?? '',
883
+ status: raw.status ?? 'active',
884
+ };
885
+ }
886
+ }
887
+
605
888
  class ApiResponse {
606
889
  status;
607
890
  data;
@@ -1827,7 +2110,14 @@ class TenantLoginComponent {
1827
2110
  }
1828
2111
  // Auto-select if user has only one tenant
1829
2112
  if (this.memberships.length === 1 && this.autoSelectSingleTenant) {
1830
- await this.selectAndContinue(this.memberships[0]);
2113
+ const m = this.memberships[0];
2114
+ // If login already returned a tenant-scoped token (via membership in response),
2115
+ // just emit — no need to call select-tenant again.
2116
+ if (loginResult?.membership) {
2117
+ this.tenantSelected.emit({ tenantId: m.tenant_id, tenantSlug: m.slug, role: m.role });
2118
+ return;
2119
+ }
2120
+ await this.selectAndContinue(m);
1831
2121
  }
1832
2122
  else {
1833
2123
  // Show tenant selector
@@ -3925,5 +4215,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
3925
4215
  * Generated bundle index. Do not edit.
3926
4216
  */
3927
4217
 
3928
- export { AUTH_PLUGIN, ApiConnectionService, ApiResponse, AuthPageComponent, AuthService, CsrfService, DbService, FilesService, LoginDialogComponent, MyEnvironmentModel, ProviderRegistryService, RegisterComponent, SigninStatusService, StoneScriptPHPAuth, TenantLoginComponent, TenantLoginDialogComponent, TenantRegisterComponent, TenantRegisterDialogComponent, TokenService, VerifyStatus, provideNgxStoneScriptPhpClient };
4218
+ export { AUTH_PLUGIN, ApiConnectionService, ApiResponse, AuthPageComponent, AuthService, CsrfService, DbService, FilesService, LoginDialogComponent, MyEnvironmentModel, ProgalaxyElabsAuth, ProviderRegistryService, RegisterComponent, SigninStatusService, StoneScriptPHPAuth, TenantLoginComponent, TenantLoginDialogComponent, TenantRegisterComponent, TenantRegisterDialogComponent, TokenService, VerifyStatus, provideNgxStoneScriptPhpClient };
3929
4219
  //# sourceMappingURL=progalaxyelabs-ngx-stonescriptphp-client.mjs.map