@hanzo/iam 0.4.1 → 0.5.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/dist/browser.js +2 -2
- package/dist/browser.js.map +1 -1
- package/dist/client.d.ts +21 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +51 -2
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/nextauth.d.ts +10 -12
- package/dist/nextauth.d.ts.map +1 -1
- package/dist/nextauth.js +11 -12
- package/dist/nextauth.js.map +1 -1
- package/dist/react.d.ts +87 -1
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +420 -0
- package/dist/react.js.map +1 -1
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -11
- package/src/browser.ts +2 -2
- package/src/client.ts +106 -5
- package/src/index.ts +2 -1
- package/src/nextauth.ts +13 -15
- package/src/react.ts +595 -1
- package/src/types.ts +20 -0
- package/dist/betterauth.d.ts +0 -67
- package/dist/betterauth.d.ts.map +0 -1
- package/dist/betterauth.js +0 -64
- package/dist/betterauth.js.map +0 -1
- package/dist/passport.d.ts +0 -44
- package/dist/passport.d.ts.map +0 -1
- package/dist/passport.js +0 -67
- package/dist/passport.js.map +0 -1
- package/src/betterauth.ts +0 -91
- package/src/passport.ts +0 -97
package/src/react.ts
CHANGED
|
@@ -46,7 +46,7 @@ import type { ReactNode } from "react";
|
|
|
46
46
|
import { BrowserIamSdk } from "./browser.js";
|
|
47
47
|
import type { BrowserIamConfig } from "./browser.js";
|
|
48
48
|
import { IamClient } from "./client.js";
|
|
49
|
-
import type { IamUser, IamOrganization, IamProject, TokenResponse } from "./types.js";
|
|
49
|
+
import type { IamUser, IamOrganization, IamInvitation, IamProject, TokenResponse } from "./types.js";
|
|
50
50
|
|
|
51
51
|
// ---------------------------------------------------------------------------
|
|
52
52
|
// Types
|
|
@@ -589,6 +589,200 @@ export function useIamToken(): {
|
|
|
589
589
|
};
|
|
590
590
|
}
|
|
591
591
|
|
|
592
|
+
// ---------------------------------------------------------------------------
|
|
593
|
+
// useOrgManagement
|
|
594
|
+
// ---------------------------------------------------------------------------
|
|
595
|
+
|
|
596
|
+
export interface OrgManagementState {
|
|
597
|
+
/** Create a new organization. */
|
|
598
|
+
createOrg: (org: Partial<IamOrganization>) => Promise<void>;
|
|
599
|
+
/** Update an existing organization. */
|
|
600
|
+
updateOrg: (org: Partial<IamOrganization>) => Promise<void>;
|
|
601
|
+
/** Delete an organization by owner and name. */
|
|
602
|
+
deleteOrg: (org: { owner: string; name: string }) => Promise<void>;
|
|
603
|
+
/** Whether a mutation is in progress. */
|
|
604
|
+
isLoading: boolean;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Manage organization CRUD operations.
|
|
609
|
+
*
|
|
610
|
+
* Provides create, update, and delete methods that call the IAM API
|
|
611
|
+
* using the current user's access token.
|
|
612
|
+
*/
|
|
613
|
+
export function useOrgManagement(): OrgManagementState {
|
|
614
|
+
const { config, accessToken } = useIam();
|
|
615
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
616
|
+
|
|
617
|
+
const client = useMemo(
|
|
618
|
+
() =>
|
|
619
|
+
new IamClient({
|
|
620
|
+
serverUrl: config.serverUrl,
|
|
621
|
+
clientId: config.clientId,
|
|
622
|
+
}),
|
|
623
|
+
[config.serverUrl, config.clientId],
|
|
624
|
+
);
|
|
625
|
+
|
|
626
|
+
const createOrg = useCallback(
|
|
627
|
+
async (org: Partial<IamOrganization>) => {
|
|
628
|
+
setIsLoading(true);
|
|
629
|
+
try {
|
|
630
|
+
await client.createOrganization(org, accessToken ?? undefined);
|
|
631
|
+
} finally {
|
|
632
|
+
setIsLoading(false);
|
|
633
|
+
}
|
|
634
|
+
},
|
|
635
|
+
[client, accessToken],
|
|
636
|
+
);
|
|
637
|
+
|
|
638
|
+
const updateOrg = useCallback(
|
|
639
|
+
async (org: Partial<IamOrganization>) => {
|
|
640
|
+
setIsLoading(true);
|
|
641
|
+
try {
|
|
642
|
+
await client.updateOrganization(org, accessToken ?? undefined);
|
|
643
|
+
} finally {
|
|
644
|
+
setIsLoading(false);
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
[client, accessToken],
|
|
648
|
+
);
|
|
649
|
+
|
|
650
|
+
const deleteOrg = useCallback(
|
|
651
|
+
async (org: { owner: string; name: string }) => {
|
|
652
|
+
setIsLoading(true);
|
|
653
|
+
try {
|
|
654
|
+
await client.deleteOrganization(org, accessToken ?? undefined);
|
|
655
|
+
} finally {
|
|
656
|
+
setIsLoading(false);
|
|
657
|
+
}
|
|
658
|
+
},
|
|
659
|
+
[client, accessToken],
|
|
660
|
+
);
|
|
661
|
+
|
|
662
|
+
return { createOrg, updateOrg, deleteOrg, isLoading };
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// ---------------------------------------------------------------------------
|
|
666
|
+
// useInvitations
|
|
667
|
+
// ---------------------------------------------------------------------------
|
|
668
|
+
|
|
669
|
+
export interface InvitationsState {
|
|
670
|
+
/** All invitations for the organization. */
|
|
671
|
+
invitations: IamInvitation[];
|
|
672
|
+
/** Create a new invitation. */
|
|
673
|
+
createInvite: (invitation: Partial<IamInvitation>) => Promise<void>;
|
|
674
|
+
/** Send an existing invitation. */
|
|
675
|
+
sendInvite: (invitation: { owner: string; name: string }) => Promise<void>;
|
|
676
|
+
/** Verify an invitation code. */
|
|
677
|
+
verifyInvite: (code: string) => Promise<IamInvitation | null>;
|
|
678
|
+
/** Whether invitations are loading. */
|
|
679
|
+
isLoading: boolean;
|
|
680
|
+
/** Re-fetch the invitations list. */
|
|
681
|
+
refresh: () => Promise<void>;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Manage invitations for an organization.
|
|
686
|
+
*
|
|
687
|
+
* Fetches the invitation list on mount and provides create, send,
|
|
688
|
+
* and verify methods using the current user's access token.
|
|
689
|
+
*/
|
|
690
|
+
export function useInvitations(orgName: string): InvitationsState {
|
|
691
|
+
const { config, accessToken, isAuthenticated } = useIam();
|
|
692
|
+
const [invitations, setInvitations] = useState<IamInvitation[]>([]);
|
|
693
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
694
|
+
|
|
695
|
+
const client = useMemo(
|
|
696
|
+
() =>
|
|
697
|
+
new IamClient({
|
|
698
|
+
serverUrl: config.serverUrl,
|
|
699
|
+
clientId: config.clientId,
|
|
700
|
+
}),
|
|
701
|
+
[config.serverUrl, config.clientId],
|
|
702
|
+
);
|
|
703
|
+
|
|
704
|
+
const fetchInvitations = useCallback(async () => {
|
|
705
|
+
if (!isAuthenticated || !accessToken || !orgName) return;
|
|
706
|
+
setIsLoading(true);
|
|
707
|
+
try {
|
|
708
|
+
const data = await client.getInvitations(orgName, accessToken);
|
|
709
|
+
setInvitations(data);
|
|
710
|
+
} catch {
|
|
711
|
+
setInvitations([]);
|
|
712
|
+
} finally {
|
|
713
|
+
setIsLoading(false);
|
|
714
|
+
}
|
|
715
|
+
}, [client, orgName, accessToken, isAuthenticated]);
|
|
716
|
+
|
|
717
|
+
// Fetch invitations on mount and when orgName changes
|
|
718
|
+
useEffect(() => {
|
|
719
|
+
if (!isAuthenticated || !accessToken || !orgName) {
|
|
720
|
+
setInvitations([]);
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
let cancelled = false;
|
|
725
|
+
|
|
726
|
+
const load = async () => {
|
|
727
|
+
setIsLoading(true);
|
|
728
|
+
try {
|
|
729
|
+
const data = await client.getInvitations(orgName, accessToken);
|
|
730
|
+
if (!cancelled) setInvitations(data);
|
|
731
|
+
} catch {
|
|
732
|
+
if (!cancelled) setInvitations([]);
|
|
733
|
+
} finally {
|
|
734
|
+
if (!cancelled) setIsLoading(false);
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
load();
|
|
739
|
+
return () => {
|
|
740
|
+
cancelled = true;
|
|
741
|
+
};
|
|
742
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
743
|
+
}, [isAuthenticated, accessToken, orgName, config.serverUrl, config.clientId]);
|
|
744
|
+
|
|
745
|
+
const createInvite = useCallback(
|
|
746
|
+
async (invitation: Partial<IamInvitation>) => {
|
|
747
|
+
setIsLoading(true);
|
|
748
|
+
try {
|
|
749
|
+
await client.createInvitation(invitation, accessToken ?? undefined);
|
|
750
|
+
await fetchInvitations();
|
|
751
|
+
} finally {
|
|
752
|
+
setIsLoading(false);
|
|
753
|
+
}
|
|
754
|
+
},
|
|
755
|
+
[client, accessToken, fetchInvitations],
|
|
756
|
+
);
|
|
757
|
+
|
|
758
|
+
const sendInvite = useCallback(
|
|
759
|
+
async (invitation: { owner: string; name: string }) => {
|
|
760
|
+
setIsLoading(true);
|
|
761
|
+
try {
|
|
762
|
+
await client.sendInvitation(invitation, accessToken ?? undefined);
|
|
763
|
+
} finally {
|
|
764
|
+
setIsLoading(false);
|
|
765
|
+
}
|
|
766
|
+
},
|
|
767
|
+
[client, accessToken],
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
const verifyInvite = useCallback(
|
|
771
|
+
async (code: string): Promise<IamInvitation | null> => {
|
|
772
|
+
setIsLoading(true);
|
|
773
|
+
try {
|
|
774
|
+
const resp = await client.verifyInvitation(code, accessToken ?? undefined);
|
|
775
|
+
return resp.data ?? null;
|
|
776
|
+
} finally {
|
|
777
|
+
setIsLoading(false);
|
|
778
|
+
}
|
|
779
|
+
},
|
|
780
|
+
[client, accessToken],
|
|
781
|
+
);
|
|
782
|
+
|
|
783
|
+
return { invitations, createInvite, sendInvite, verifyInvite, isLoading, refresh: fetchInvitations };
|
|
784
|
+
}
|
|
785
|
+
|
|
592
786
|
// Re-export context for advanced use
|
|
593
787
|
export { IamContext };
|
|
594
788
|
|
|
@@ -717,3 +911,403 @@ export function OrgProjectSwitcher({
|
|
|
717
911
|
: null,
|
|
718
912
|
);
|
|
719
913
|
}
|
|
914
|
+
|
|
915
|
+
// ---------------------------------------------------------------------------
|
|
916
|
+
// UserOrgMenu — shared org switcher + user menu for all Hanzo apps
|
|
917
|
+
// ---------------------------------------------------------------------------
|
|
918
|
+
|
|
919
|
+
export interface UserOrgMenuProps {
|
|
920
|
+
/** Additional CSS class for the outer container. */
|
|
921
|
+
className?: string;
|
|
922
|
+
/** Called when org changes. Use to sync external state (e.g., tenantStore). */
|
|
923
|
+
onOrgChange?: (orgId: string) => void;
|
|
924
|
+
/** Called when user clicks logout. */
|
|
925
|
+
onLogout?: () => void;
|
|
926
|
+
/** Whether to show the "Create Organization" option. Defaults to true. */
|
|
927
|
+
showCreateOrg?: boolean;
|
|
928
|
+
/** Optional endpoint for org creation (defaults to IAM's /api/add-organization). */
|
|
929
|
+
createOrgEndpoint?: string;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* Shared user menu + organization switcher for all Hanzo apps.
|
|
934
|
+
*
|
|
935
|
+
* Shows current user info (name, email, avatar), a dropdown with org list,
|
|
936
|
+
* "Create Organization" option, and logout button. Uses only `@hanzo/iam`
|
|
937
|
+
* hooks — no external UI library required.
|
|
938
|
+
*
|
|
939
|
+
* @example
|
|
940
|
+
* ```tsx
|
|
941
|
+
* import { UserOrgMenu } from '@hanzo/iam/react'
|
|
942
|
+
*
|
|
943
|
+
* function TopBar() {
|
|
944
|
+
* return (
|
|
945
|
+
* <nav>
|
|
946
|
+
* <UserOrgMenu
|
|
947
|
+
* onOrgChange={(orgId) => myStore.setOrg(orgId)}
|
|
948
|
+
* onLogout={() => router.push('/login')}
|
|
949
|
+
* />
|
|
950
|
+
* </nav>
|
|
951
|
+
* )
|
|
952
|
+
* }
|
|
953
|
+
* ```
|
|
954
|
+
*/
|
|
955
|
+
export function UserOrgMenu({
|
|
956
|
+
className = "",
|
|
957
|
+
onOrgChange,
|
|
958
|
+
onLogout,
|
|
959
|
+
showCreateOrg = true,
|
|
960
|
+
createOrgEndpoint,
|
|
961
|
+
}: UserOrgMenuProps) {
|
|
962
|
+
const { config, isAuthenticated, accessToken, user, logout } = useIam();
|
|
963
|
+
const orgState = useOrganizations();
|
|
964
|
+
const [open, setOpen] = useState(false);
|
|
965
|
+
const [createOpen, setCreateOpen] = useState(false);
|
|
966
|
+
const [newOrgName, setNewOrgName] = useState("");
|
|
967
|
+
const [newOrgDisplay, setNewOrgDisplay] = useState("");
|
|
968
|
+
const [creating, setCreating] = useState(false);
|
|
969
|
+
const [error, setError] = useState<string | null>(null);
|
|
970
|
+
const menuRef = useRef<HTMLDivElement>(null);
|
|
971
|
+
|
|
972
|
+
// Close on outside click
|
|
973
|
+
useEffect(() => {
|
|
974
|
+
const handler = (e: MouseEvent) => {
|
|
975
|
+
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
|
976
|
+
setOpen(false);
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
document.addEventListener("mousedown", handler);
|
|
980
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
981
|
+
}, []);
|
|
982
|
+
|
|
983
|
+
const handleSwitchOrg = useCallback(
|
|
984
|
+
(orgId: string) => {
|
|
985
|
+
orgState.switchOrg(orgId);
|
|
986
|
+
onOrgChange?.(orgId);
|
|
987
|
+
setOpen(false);
|
|
988
|
+
},
|
|
989
|
+
[orgState, onOrgChange],
|
|
990
|
+
);
|
|
991
|
+
|
|
992
|
+
const handleLogout = useCallback(() => {
|
|
993
|
+
setOpen(false);
|
|
994
|
+
if (onLogout) {
|
|
995
|
+
onLogout();
|
|
996
|
+
} else {
|
|
997
|
+
logout?.();
|
|
998
|
+
}
|
|
999
|
+
}, [onLogout, logout]);
|
|
1000
|
+
|
|
1001
|
+
const handleCreateOrg = useCallback(async () => {
|
|
1002
|
+
const name = newOrgName.trim();
|
|
1003
|
+
if (!name) return;
|
|
1004
|
+
|
|
1005
|
+
setCreating(true);
|
|
1006
|
+
setError(null);
|
|
1007
|
+
|
|
1008
|
+
try {
|
|
1009
|
+
const client = new IamClient({
|
|
1010
|
+
serverUrl: config.serverUrl,
|
|
1011
|
+
clientId: config.clientId,
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
if (createOrgEndpoint) {
|
|
1015
|
+
// Use custom endpoint (e.g., playground's /v1/orgs)
|
|
1016
|
+
const res = await fetch(createOrgEndpoint, {
|
|
1017
|
+
method: "POST",
|
|
1018
|
+
headers: {
|
|
1019
|
+
"Content-Type": "application/json",
|
|
1020
|
+
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),
|
|
1021
|
+
},
|
|
1022
|
+
body: JSON.stringify({
|
|
1023
|
+
name,
|
|
1024
|
+
displayName: newOrgDisplay.trim() || name,
|
|
1025
|
+
}),
|
|
1026
|
+
});
|
|
1027
|
+
if (!res.ok) {
|
|
1028
|
+
const body = await res.json().catch(() => ({}));
|
|
1029
|
+
throw new Error(body.error || body.msg || `HTTP ${res.status}`);
|
|
1030
|
+
}
|
|
1031
|
+
} else {
|
|
1032
|
+
// Use IAM directly
|
|
1033
|
+
await client.createOrganization(
|
|
1034
|
+
{ owner: "admin", name, displayName: newOrgDisplay.trim() || name },
|
|
1035
|
+
accessToken ?? undefined,
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// Switch to new org
|
|
1040
|
+
orgState.switchOrg(name);
|
|
1041
|
+
onOrgChange?.(name);
|
|
1042
|
+
setNewOrgName("");
|
|
1043
|
+
setNewOrgDisplay("");
|
|
1044
|
+
setCreateOpen(false);
|
|
1045
|
+
setOpen(false);
|
|
1046
|
+
|
|
1047
|
+
// Reload to refresh org list
|
|
1048
|
+
window.location.reload();
|
|
1049
|
+
} catch (e) {
|
|
1050
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
1051
|
+
} finally {
|
|
1052
|
+
setCreating(false);
|
|
1053
|
+
}
|
|
1054
|
+
}, [newOrgName, newOrgDisplay, config, accessToken, createOrgEndpoint, orgState, onOrgChange]);
|
|
1055
|
+
|
|
1056
|
+
if (!isAuthenticated) return null;
|
|
1057
|
+
|
|
1058
|
+
const orgs = orgState.organizations ?? [];
|
|
1059
|
+
const currentLabel =
|
|
1060
|
+
orgs.find((o) => o.name === orgState.currentOrgId)?.displayName ??
|
|
1061
|
+
orgState.currentOrgId ??
|
|
1062
|
+
"Select org";
|
|
1063
|
+
|
|
1064
|
+
const userName = user?.displayName || user?.name || user?.email || "User";
|
|
1065
|
+
const userEmail = user?.email || "";
|
|
1066
|
+
const userAvatar = user?.avatar || "";
|
|
1067
|
+
|
|
1068
|
+
// Inline styles (no external CSS dependencies)
|
|
1069
|
+
const menuStyle: React.CSSProperties = {
|
|
1070
|
+
position: "absolute",
|
|
1071
|
+
top: "100%",
|
|
1072
|
+
right: 0,
|
|
1073
|
+
marginTop: 4,
|
|
1074
|
+
minWidth: 240,
|
|
1075
|
+
borderRadius: 8,
|
|
1076
|
+
border: "1px solid var(--border, #333)",
|
|
1077
|
+
background: "var(--popover, #1a1a1a)",
|
|
1078
|
+
color: "var(--popover-foreground, #fff)",
|
|
1079
|
+
boxShadow: "0 8px 32px rgba(0,0,0,0.4)",
|
|
1080
|
+
zIndex: 50,
|
|
1081
|
+
overflow: "hidden",
|
|
1082
|
+
};
|
|
1083
|
+
|
|
1084
|
+
const itemStyle: React.CSSProperties = {
|
|
1085
|
+
display: "flex",
|
|
1086
|
+
alignItems: "center",
|
|
1087
|
+
gap: 8,
|
|
1088
|
+
padding: "8px 12px",
|
|
1089
|
+
fontSize: 13,
|
|
1090
|
+
cursor: "pointer",
|
|
1091
|
+
transition: "background 0.1s",
|
|
1092
|
+
width: "100%",
|
|
1093
|
+
border: "none",
|
|
1094
|
+
background: "transparent",
|
|
1095
|
+
color: "inherit",
|
|
1096
|
+
textAlign: "left",
|
|
1097
|
+
};
|
|
1098
|
+
|
|
1099
|
+
const activeItemStyle: React.CSSProperties = {
|
|
1100
|
+
...itemStyle,
|
|
1101
|
+
background: "var(--accent, #2a2a2a)",
|
|
1102
|
+
};
|
|
1103
|
+
|
|
1104
|
+
const separatorStyle: React.CSSProperties = {
|
|
1105
|
+
height: 1,
|
|
1106
|
+
background: "var(--border, #333)",
|
|
1107
|
+
margin: "4px 0",
|
|
1108
|
+
};
|
|
1109
|
+
|
|
1110
|
+
const labelStyle: React.CSSProperties = {
|
|
1111
|
+
padding: "6px 12px",
|
|
1112
|
+
fontSize: 11,
|
|
1113
|
+
fontWeight: 600,
|
|
1114
|
+
textTransform: "uppercase" as const,
|
|
1115
|
+
letterSpacing: "0.05em",
|
|
1116
|
+
color: "var(--muted-foreground, #888)",
|
|
1117
|
+
};
|
|
1118
|
+
|
|
1119
|
+
return createElement(
|
|
1120
|
+
"div",
|
|
1121
|
+
{ ref: menuRef, className: `relative ${className}`, style: { position: "relative" } },
|
|
1122
|
+
|
|
1123
|
+
// Trigger button
|
|
1124
|
+
createElement(
|
|
1125
|
+
"button",
|
|
1126
|
+
{
|
|
1127
|
+
onClick: () => setOpen(!open),
|
|
1128
|
+
style: {
|
|
1129
|
+
display: "flex",
|
|
1130
|
+
alignItems: "center",
|
|
1131
|
+
gap: 8,
|
|
1132
|
+
padding: "6px 10px",
|
|
1133
|
+
borderRadius: 6,
|
|
1134
|
+
border: "none",
|
|
1135
|
+
background: "transparent",
|
|
1136
|
+
cursor: "pointer",
|
|
1137
|
+
color: "inherit",
|
|
1138
|
+
fontSize: 13,
|
|
1139
|
+
fontWeight: 500,
|
|
1140
|
+
},
|
|
1141
|
+
"aria-label": "User menu",
|
|
1142
|
+
},
|
|
1143
|
+
userAvatar
|
|
1144
|
+
? createElement("img", {
|
|
1145
|
+
src: userAvatar,
|
|
1146
|
+
alt: userName,
|
|
1147
|
+
style: { width: 24, height: 24, borderRadius: "50%", objectFit: "cover" as const },
|
|
1148
|
+
})
|
|
1149
|
+
: createElement(
|
|
1150
|
+
"div",
|
|
1151
|
+
{
|
|
1152
|
+
style: {
|
|
1153
|
+
width: 24,
|
|
1154
|
+
height: 24,
|
|
1155
|
+
borderRadius: "50%",
|
|
1156
|
+
background: "var(--primary, #3b82f6)",
|
|
1157
|
+
display: "flex",
|
|
1158
|
+
alignItems: "center",
|
|
1159
|
+
justifyContent: "center",
|
|
1160
|
+
fontSize: 11,
|
|
1161
|
+
fontWeight: 600,
|
|
1162
|
+
color: "#fff",
|
|
1163
|
+
},
|
|
1164
|
+
},
|
|
1165
|
+
userName.charAt(0).toUpperCase(),
|
|
1166
|
+
),
|
|
1167
|
+
createElement("span", { style: { maxWidth: 120, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" as const } }, currentLabel),
|
|
1168
|
+
createElement("span", { style: { fontSize: 10, opacity: 0.5 } }, open ? "\u25B2" : "\u25BC"),
|
|
1169
|
+
),
|
|
1170
|
+
|
|
1171
|
+
// Dropdown menu
|
|
1172
|
+
open &&
|
|
1173
|
+
createElement(
|
|
1174
|
+
"div",
|
|
1175
|
+
{ style: menuStyle },
|
|
1176
|
+
|
|
1177
|
+
// User info section
|
|
1178
|
+
createElement(
|
|
1179
|
+
"div",
|
|
1180
|
+
{ style: { padding: "10px 12px", borderBottom: "1px solid var(--border, #333)" } },
|
|
1181
|
+
createElement("div", { style: { fontSize: 13, fontWeight: 600 } }, userName),
|
|
1182
|
+
userEmail && createElement("div", { style: { fontSize: 11, opacity: 0.6, marginTop: 2 } }, userEmail),
|
|
1183
|
+
),
|
|
1184
|
+
|
|
1185
|
+
// Organization section
|
|
1186
|
+
createElement("div", { style: labelStyle }, "Organization"),
|
|
1187
|
+
...orgs.map((org) =>
|
|
1188
|
+
createElement(
|
|
1189
|
+
"button",
|
|
1190
|
+
{
|
|
1191
|
+
key: org.name,
|
|
1192
|
+
onClick: () => handleSwitchOrg(org.name),
|
|
1193
|
+
style: org.name === orgState.currentOrgId ? activeItemStyle : itemStyle,
|
|
1194
|
+
onMouseEnter: (e: React.MouseEvent<HTMLButtonElement>) => { (e.target as HTMLElement).style.background = "var(--accent, #2a2a2a)"; },
|
|
1195
|
+
onMouseLeave: (e: React.MouseEvent<HTMLButtonElement>) => { if (org.name !== orgState.currentOrgId) (e.target as HTMLElement).style.background = "transparent"; },
|
|
1196
|
+
},
|
|
1197
|
+
org.name === orgState.currentOrgId ? "\u2713 " : " ",
|
|
1198
|
+
org.displayName || org.name,
|
|
1199
|
+
),
|
|
1200
|
+
),
|
|
1201
|
+
|
|
1202
|
+
// Create org option
|
|
1203
|
+
showCreateOrg &&
|
|
1204
|
+
createElement(
|
|
1205
|
+
"div",
|
|
1206
|
+
null,
|
|
1207
|
+
createElement("div", { style: separatorStyle }),
|
|
1208
|
+
!createOpen
|
|
1209
|
+
? createElement(
|
|
1210
|
+
"button",
|
|
1211
|
+
{
|
|
1212
|
+
onClick: () => setCreateOpen(true),
|
|
1213
|
+
style: itemStyle,
|
|
1214
|
+
onMouseEnter: (e: React.MouseEvent<HTMLButtonElement>) => { (e.target as HTMLElement).style.background = "var(--accent, #2a2a2a)"; },
|
|
1215
|
+
onMouseLeave: (e: React.MouseEvent<HTMLButtonElement>) => { (e.target as HTMLElement).style.background = "transparent"; },
|
|
1216
|
+
},
|
|
1217
|
+
"+ Create Organization",
|
|
1218
|
+
)
|
|
1219
|
+
: createElement(
|
|
1220
|
+
"div",
|
|
1221
|
+
{ style: { padding: "8px 12px" } },
|
|
1222
|
+
createElement("input", {
|
|
1223
|
+
type: "text",
|
|
1224
|
+
placeholder: "org-name",
|
|
1225
|
+
value: newOrgName,
|
|
1226
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => setNewOrgName(e.target.value),
|
|
1227
|
+
style: {
|
|
1228
|
+
width: "100%",
|
|
1229
|
+
padding: "6px 8px",
|
|
1230
|
+
fontSize: 12,
|
|
1231
|
+
borderRadius: 4,
|
|
1232
|
+
border: "1px solid var(--border, #333)",
|
|
1233
|
+
background: "var(--background, #111)",
|
|
1234
|
+
color: "inherit",
|
|
1235
|
+
marginBottom: 4,
|
|
1236
|
+
},
|
|
1237
|
+
disabled: creating,
|
|
1238
|
+
}),
|
|
1239
|
+
createElement("input", {
|
|
1240
|
+
type: "text",
|
|
1241
|
+
placeholder: "Display Name",
|
|
1242
|
+
value: newOrgDisplay,
|
|
1243
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => setNewOrgDisplay(e.target.value),
|
|
1244
|
+
style: {
|
|
1245
|
+
width: "100%",
|
|
1246
|
+
padding: "6px 8px",
|
|
1247
|
+
fontSize: 12,
|
|
1248
|
+
borderRadius: 4,
|
|
1249
|
+
border: "1px solid var(--border, #333)",
|
|
1250
|
+
background: "var(--background, #111)",
|
|
1251
|
+
color: "inherit",
|
|
1252
|
+
marginBottom: 4,
|
|
1253
|
+
},
|
|
1254
|
+
disabled: creating,
|
|
1255
|
+
}),
|
|
1256
|
+
error && createElement("div", { style: { fontSize: 11, color: "#ef4444", marginBottom: 4 } }, error),
|
|
1257
|
+
createElement(
|
|
1258
|
+
"div",
|
|
1259
|
+
{ style: { display: "flex", gap: 4 } },
|
|
1260
|
+
createElement(
|
|
1261
|
+
"button",
|
|
1262
|
+
{
|
|
1263
|
+
onClick: handleCreateOrg,
|
|
1264
|
+
disabled: creating || !newOrgName.trim(),
|
|
1265
|
+
style: {
|
|
1266
|
+
flex: 1,
|
|
1267
|
+
padding: "5px 8px",
|
|
1268
|
+
fontSize: 12,
|
|
1269
|
+
borderRadius: 4,
|
|
1270
|
+
border: "none",
|
|
1271
|
+
background: "var(--primary, #3b82f6)",
|
|
1272
|
+
color: "#fff",
|
|
1273
|
+
cursor: creating ? "wait" : "pointer",
|
|
1274
|
+
opacity: creating || !newOrgName.trim() ? 0.5 : 1,
|
|
1275
|
+
},
|
|
1276
|
+
},
|
|
1277
|
+
creating ? "Creating..." : "Create",
|
|
1278
|
+
),
|
|
1279
|
+
createElement(
|
|
1280
|
+
"button",
|
|
1281
|
+
{
|
|
1282
|
+
onClick: () => { setCreateOpen(false); setError(null); },
|
|
1283
|
+
style: {
|
|
1284
|
+
padding: "5px 8px",
|
|
1285
|
+
fontSize: 12,
|
|
1286
|
+
borderRadius: 4,
|
|
1287
|
+
border: "1px solid var(--border, #333)",
|
|
1288
|
+
background: "transparent",
|
|
1289
|
+
color: "inherit",
|
|
1290
|
+
cursor: "pointer",
|
|
1291
|
+
},
|
|
1292
|
+
},
|
|
1293
|
+
"Cancel",
|
|
1294
|
+
),
|
|
1295
|
+
),
|
|
1296
|
+
),
|
|
1297
|
+
),
|
|
1298
|
+
|
|
1299
|
+
// Logout
|
|
1300
|
+
createElement("div", { style: separatorStyle }),
|
|
1301
|
+
createElement(
|
|
1302
|
+
"button",
|
|
1303
|
+
{
|
|
1304
|
+
onClick: handleLogout,
|
|
1305
|
+
style: { ...itemStyle, color: "var(--destructive, #ef4444)" },
|
|
1306
|
+
onMouseEnter: (e: React.MouseEvent<HTMLButtonElement>) => { (e.target as HTMLElement).style.background = "var(--accent, #2a2a2a)"; },
|
|
1307
|
+
onMouseLeave: (e: React.MouseEvent<HTMLButtonElement>) => { (e.target as HTMLElement).style.background = "transparent"; },
|
|
1308
|
+
},
|
|
1309
|
+
"Sign out",
|
|
1310
|
+
),
|
|
1311
|
+
),
|
|
1312
|
+
);
|
|
1313
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -226,6 +226,26 @@ export type IamOrder = Order;
|
|
|
226
226
|
export type IamUsageRecord = UsageRecord;
|
|
227
227
|
export type IamUsageSummary = UsageSummary;
|
|
228
228
|
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
// Invitation
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
export type IamInvitation = {
|
|
234
|
+
owner: string;
|
|
235
|
+
name: string;
|
|
236
|
+
displayName?: string;
|
|
237
|
+
code: string;
|
|
238
|
+
quota: number;
|
|
239
|
+
usedCount: number;
|
|
240
|
+
application?: string;
|
|
241
|
+
email?: string;
|
|
242
|
+
phone?: string;
|
|
243
|
+
signupGroup?: string;
|
|
244
|
+
state?: string;
|
|
245
|
+
createdTime?: string;
|
|
246
|
+
updatedTime?: string;
|
|
247
|
+
};
|
|
248
|
+
|
|
229
249
|
// ---------------------------------------------------------------------------
|
|
230
250
|
// Project
|
|
231
251
|
// ---------------------------------------------------------------------------
|
package/dist/betterauth.d.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BetterAuth SSO provider configuration for IAM.
|
|
3
|
-
*
|
|
4
|
-
* Returns a provider config object compatible with BetterAuth's
|
|
5
|
-
* `socialProviders` or generic OAuth plugin.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```ts
|
|
9
|
-
* import { betterAuth } from "better-auth";
|
|
10
|
-
* import { iamProvider } from "@hanzo/iam/betterauth";
|
|
11
|
-
*
|
|
12
|
-
* export const auth = betterAuth({
|
|
13
|
-
* socialProviders: [
|
|
14
|
-
* iamProvider({
|
|
15
|
-
* serverUrl: process.env.IAM_SERVER_URL!,
|
|
16
|
-
* clientId: process.env.IAM_CLIENT_ID!,
|
|
17
|
-
* clientSecret: process.env.IAM_CLIENT_SECRET!,
|
|
18
|
-
* }),
|
|
19
|
-
* ],
|
|
20
|
-
* });
|
|
21
|
-
* ```
|
|
22
|
-
*
|
|
23
|
-
* @packageDocumentation
|
|
24
|
-
*/
|
|
25
|
-
import type { IamConfig } from "./types.js";
|
|
26
|
-
export interface IamSocialProvider {
|
|
27
|
-
id: string;
|
|
28
|
-
name: string;
|
|
29
|
-
type: "oidc";
|
|
30
|
-
issuer: string;
|
|
31
|
-
clientId: string;
|
|
32
|
-
clientSecret?: string;
|
|
33
|
-
authorization: {
|
|
34
|
-
url: string;
|
|
35
|
-
params: {
|
|
36
|
-
scope: string;
|
|
37
|
-
};
|
|
38
|
-
};
|
|
39
|
-
token: {
|
|
40
|
-
url: string;
|
|
41
|
-
};
|
|
42
|
-
userinfo: {
|
|
43
|
-
url: string;
|
|
44
|
-
};
|
|
45
|
-
profile: (profile: Record<string, unknown>) => {
|
|
46
|
-
id: string;
|
|
47
|
-
name: string;
|
|
48
|
-
email: string;
|
|
49
|
-
image: string | null;
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Create a BetterAuth-compatible social provider for IAM.
|
|
54
|
-
*
|
|
55
|
-
* Works with BetterAuth's SSO plugin or generic OAuth integration.
|
|
56
|
-
* Uses standard OIDC endpoints.
|
|
57
|
-
*/
|
|
58
|
-
export declare function iamProvider(config: IamConfig & {
|
|
59
|
-
redirectUri?: string;
|
|
60
|
-
}): IamSocialProvider;
|
|
61
|
-
/** @deprecated Use iamProvider instead */
|
|
62
|
-
export { iamProvider as hanzoIamProvider };
|
|
63
|
-
/** @deprecated Use iamProvider instead */
|
|
64
|
-
export { iamProvider as hanzoIamSocialProvider };
|
|
65
|
-
/** @deprecated Use IamSocialProvider instead */
|
|
66
|
-
export type { IamSocialProvider as HanzoIamSocialProvider };
|
|
67
|
-
//# sourceMappingURL=betterauth.d.ts.map
|
package/dist/betterauth.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"betterauth.d.ts","sourceRoot":"","sources":["../src/betterauth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;IAC1D,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACvB,QAAQ,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1B,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK;QAC7C,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;KACtB,CAAC;CACH;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,SAAS,GAAG;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3C,iBAAiB,CA6BnB;AAGD,0CAA0C;AAC1C,OAAO,EAAE,WAAW,IAAI,gBAAgB,EAAE,CAAC;AAC3C,0CAA0C;AAC1C,OAAO,EAAE,WAAW,IAAI,sBAAsB,EAAE,CAAC;AACjD,gDAAgD;AAChD,YAAY,EAAE,iBAAiB,IAAI,sBAAsB,EAAE,CAAC"}
|