@aethex.os/passport 1.0.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/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # @aethex.os/passport
2
+
3
+ OAuth federation for multi-provider auth — let users log in with GitHub, Discord, Google, Roblox, or any provider, and link them all to a single unified identity. Storage-adapter pattern means it works with any database.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @aethex.os/passport
9
+ ```
10
+
11
+ ## Concept
12
+
13
+ Instead of one user per OAuth provider, `@aethex.os/passport` maintains a **Foundation Passport** — one canonical user record that can have multiple OAuth providers attached to it. Log in with Discord, later link GitHub, they're the same user.
14
+
15
+ ## Setup with Supabase
16
+
17
+ ```ts
18
+ import { createClient } from "@supabase/supabase-js";
19
+ import { createPassportManager, createSupabasePassportAdapter } from "@aethex.os/passport";
20
+
21
+ const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE!);
22
+
23
+ const passport = createPassportManager({
24
+ storage: createSupabasePassportAdapter(supabase),
25
+ });
26
+ ```
27
+
28
+ **Required Supabase tables:**
29
+
30
+ ```sql
31
+ create table user_profiles (
32
+ id uuid primary key default gen_random_uuid(),
33
+ email text unique not null,
34
+ username text unique not null,
35
+ full_name text,
36
+ avatar_url text,
37
+ created_at timestamptz default now()
38
+ );
39
+
40
+ create table provider_identities (
41
+ user_id uuid references user_profiles(id),
42
+ provider text not null,
43
+ provider_user_id text not null,
44
+ email text,
45
+ username text,
46
+ avatar_url text,
47
+ linked_at timestamptz default now(),
48
+ primary key (user_id, provider)
49
+ );
50
+ ```
51
+
52
+ ## Login / sign-up (federation)
53
+
54
+ ```ts
55
+ // In your Discord/GitHub/Google OAuth callback
56
+ const result = await passport.federateOAuthUser("discord", {
57
+ id: discordUser.id,
58
+ email: discordUser.email,
59
+ username: discordUser.username,
60
+ name: discordUser.global_name,
61
+ avatar: `https://cdn.discordapp.com/avatars/${discordUser.id}/${discordUser.avatar}.webp`,
62
+ });
63
+
64
+ console.log(result.userId); // Foundation Passport ID
65
+ console.log(result.isNewUser); // true if just signed up
66
+ ```
67
+
68
+ ## Link an additional provider
69
+
70
+ ```ts
71
+ // User is already logged in, now linking GitHub to their account
72
+ await passport.linkProvider(currentUserId, "github", {
73
+ id: githubUser.id,
74
+ email: githubUser.email,
75
+ username: githubUser.login,
76
+ avatar: githubUser.avatar_url,
77
+ });
78
+ ```
79
+
80
+ ## Unlink a provider
81
+
82
+ ```ts
83
+ // Prevents unlinking the last auth method
84
+ await passport.unlinkProvider(userId, "github");
85
+ ```
86
+
87
+ ## List linked providers
88
+
89
+ ```ts
90
+ const providers = await passport.getLinkedProviders(userId);
91
+ // ["discord", "github"]
92
+ ```
93
+
94
+ ## Custom storage adapter
95
+
96
+ Works with any database by implementing the `PassportStorageAdapter` interface:
97
+
98
+ ```ts
99
+ import type { PassportStorageAdapter } from "@aethex.os/passport";
100
+
101
+ const myAdapter: PassportStorageAdapter = {
102
+ findIdentity: async (provider, providerUserId) => { /* ... */ },
103
+ createUser: async (data) => { /* return new userId */ },
104
+ linkProvider: async (userId, provider, providerUserId, data) => { /* ... */ },
105
+ findUserByEmail: async (email) => { /* return userId or null */ },
106
+ listUserProviders: async (userId) => { /* return provider[] */ },
107
+ unlinkProvider: async (userId, provider) => { /* ... */ },
108
+ };
109
+
110
+ const passport = createPassportManager({ storage: myAdapter });
111
+ ```
112
+
113
+ ## Testing with in-memory adapter
114
+
115
+ ```ts
116
+ import { createPassportManager, createMemoryPassportAdapter } from "@aethex.os/passport";
117
+
118
+ const passport = createPassportManager({ storage: createMemoryPassportAdapter() });
119
+ ```
120
+
121
+ ## API
122
+
123
+ | Method | Description |
124
+ |--------|-------------|
125
+ | `passport.federateOAuthUser(provider, user)` | Login or sign-up via any OAuth provider |
126
+ | `passport.linkProvider(userId, provider, user)` | Link an additional provider to an existing user |
127
+ | `passport.unlinkProvider(userId, provider)` | Unlink a provider (guards against unlinking the last one) |
128
+ | `passport.getLinkedProviders(userId)` | List all providers linked to a user |
129
+
130
+ ---
131
+
132
+ Part of the [`@aethex.os`](https://www.npmjs.com/search?q=%40aethex.os) toolkit.
@@ -0,0 +1,3 @@
1
+ import type { PassportStorageAdapter } from "../federation";
2
+ export declare function createMemoryPassportAdapter(): PassportStorageAdapter;
3
+ //# sourceMappingURL=memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/adapters/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAA8C,MAAM,eAAe,CAAC;AAkBxG,wBAAgB,2BAA2B,IAAI,sBAAsB,CAwCpE"}
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createMemoryPassportAdapter = createMemoryPassportAdapter;
4
+ // In-memory adapter — useful for tests and prototyping
5
+ function createMemoryPassportAdapter() {
6
+ const users = [];
7
+ const identities = [];
8
+ let idCounter = 1;
9
+ return {
10
+ async findIdentity(provider, providerUserId) {
11
+ const identity = identities.find(i => i.provider === provider && i.providerUserId === providerUserId);
12
+ if (!identity)
13
+ return null;
14
+ return { userId: identity.userId, provider: identity.provider, providerUserId: identity.providerUserId };
15
+ },
16
+ async createUser(data) {
17
+ const id = String(idCounter++);
18
+ users.push({ id, ...data });
19
+ return id;
20
+ },
21
+ async linkProvider(userId, provider, providerUserId, data) {
22
+ const idx = identities.findIndex(i => i.userId === userId && i.provider === provider);
23
+ if (idx >= 0) {
24
+ identities[idx] = { userId, provider, providerUserId, data };
25
+ }
26
+ else {
27
+ identities.push({ userId, provider, providerUserId, data });
28
+ }
29
+ },
30
+ async findUserByEmail(email) {
31
+ return users.find(u => u.email === email)?.id ?? null;
32
+ },
33
+ async listUserProviders(userId) {
34
+ return identities.filter(i => i.userId === userId).map(i => i.provider);
35
+ },
36
+ async unlinkProvider(userId, provider) {
37
+ const idx = identities.findIndex(i => i.userId === userId && i.provider === provider);
38
+ if (idx >= 0)
39
+ identities.splice(idx, 1);
40
+ },
41
+ };
42
+ }
43
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/adapters/memory.ts"],"names":[],"mappings":";;AAkBA,kEAwCC;AAzCD,uDAAuD;AACvD,SAAgB,2BAA2B;IACzC,MAAM,KAAK,GAAiB,EAAE,CAAC;IAC/B,MAAM,UAAU,GAAqB,EAAE,CAAC;IACxC,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,OAAO;QACL,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,cAAc;YACzC,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,cAAc,KAAK,cAAc,CAAC,CAAC;YACtG,IAAI,CAAC,QAAQ;gBAAE,OAAO,IAAI,CAAC;YAC3B,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,cAAc,EAAE,QAAQ,CAAC,cAAc,EAAE,CAAC;QAC3G,CAAC;QAED,KAAK,CAAC,UAAU,CAAC,IAAI;YACnB,MAAM,EAAE,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;YAC/B,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;YAC5B,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,IAAI;YACvD,MAAM,GAAG,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;YACtF,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC;gBACb,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC;YAC/D,CAAC;iBAAM,CAAC;gBACN,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC;QAED,KAAK,CAAC,eAAe,CAAC,KAAK;YACzB,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,EAAE,EAAE,IAAI,IAAI,CAAC;QACxD,CAAC;QAED,KAAK,CAAC,iBAAiB,CAAC,MAAM;YAC5B,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAC1E,CAAC;QAED,KAAK,CAAC,cAAc,CAAC,MAAM,EAAE,QAAQ;YACnC,MAAM,GAAG,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;YACtF,IAAI,GAAG,IAAI,CAAC;gBAAE,UAAU,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC1C,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { PassportStorageAdapter } from "../federation";
2
+ export interface SupabaseClient {
3
+ from: (table: string) => any;
4
+ }
5
+ export declare function createSupabasePassportAdapter(supabase: SupabaseClient): PassportStorageAdapter;
6
+ //# sourceMappingURL=supabase.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/adapters/supabase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAA8C,MAAM,eAAe,CAAC;AAExG,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,GAAG,CAAC;CAC9B;AAED,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,cAAc,GAAG,sBAAsB,CAwE9F"}
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSupabasePassportAdapter = createSupabasePassportAdapter;
4
+ function createSupabasePassportAdapter(supabase) {
5
+ return {
6
+ async findIdentity(provider, providerUserId) {
7
+ const { data, error } = await supabase
8
+ .from("provider_identities")
9
+ .select("user_id")
10
+ .eq("provider", provider)
11
+ .eq("provider_user_id", providerUserId)
12
+ .single();
13
+ if (error || !data)
14
+ return null;
15
+ return { userId: data.user_id, provider, providerUserId };
16
+ },
17
+ async createUser(data) {
18
+ const { data: user, error } = await supabase
19
+ .from("user_profiles")
20
+ .insert({
21
+ email: data.email,
22
+ full_name: data.name,
23
+ avatar_url: data.avatar ?? null,
24
+ username: data.username,
25
+ created_at: new Date().toISOString(),
26
+ })
27
+ .select("id")
28
+ .single();
29
+ if (error || !user)
30
+ throw new Error(`Failed to create user: ${error?.message}`);
31
+ return user.id;
32
+ },
33
+ async linkProvider(userId, provider, providerUserId, oauthUser) {
34
+ const { error } = await supabase.from("provider_identities").upsert({
35
+ user_id: userId,
36
+ provider,
37
+ provider_user_id: providerUserId,
38
+ email: oauthUser.email,
39
+ username: oauthUser.username,
40
+ avatar_url: oauthUser.avatar,
41
+ linked_at: new Date().toISOString(),
42
+ }, { onConflict: "user_id,provider" });
43
+ if (error)
44
+ throw new Error(`Failed to link provider: ${error.message}`);
45
+ },
46
+ async findUserByEmail(email) {
47
+ const { data } = await supabase
48
+ .from("user_profiles")
49
+ .select("id")
50
+ .eq("email", email)
51
+ .single();
52
+ return data?.id ?? null;
53
+ },
54
+ async listUserProviders(userId) {
55
+ const { data } = await supabase
56
+ .from("provider_identities")
57
+ .select("provider")
58
+ .eq("user_id", userId);
59
+ return (data ?? []).map((r) => r.provider);
60
+ },
61
+ async unlinkProvider(userId, provider) {
62
+ const { error } = await supabase
63
+ .from("provider_identities")
64
+ .delete()
65
+ .eq("user_id", userId)
66
+ .eq("provider", provider);
67
+ if (error)
68
+ throw new Error(`Failed to unlink provider: ${error.message}`);
69
+ },
70
+ };
71
+ }
72
+ //# sourceMappingURL=supabase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supabase.js","sourceRoot":"","sources":["../../src/adapters/supabase.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ export type OAuthProvider = "github" | "discord" | "google" | "roblox" | "ethereum" | string;
2
+ export interface OAuthUser {
3
+ id: string;
4
+ email: string;
5
+ name?: string;
6
+ avatar?: string;
7
+ username?: string;
8
+ }
9
+ export interface FederationResult {
10
+ userId: string;
11
+ email: string;
12
+ isNewUser: boolean;
13
+ providerLinked: boolean;
14
+ }
15
+ export interface ProviderIdentity {
16
+ userId: string;
17
+ provider: OAuthProvider;
18
+ providerUserId: string;
19
+ }
20
+ export interface PassportStorageAdapter {
21
+ findIdentity(provider: OAuthProvider, providerUserId: string): Promise<ProviderIdentity | null>;
22
+ createUser(data: {
23
+ email: string;
24
+ name: string;
25
+ avatar?: string;
26
+ username: string;
27
+ }): Promise<string>;
28
+ linkProvider(userId: string, provider: OAuthProvider, providerUserId: string, data: OAuthUser): Promise<void>;
29
+ findUserByEmail(email: string): Promise<string | null>;
30
+ listUserProviders(userId: string): Promise<OAuthProvider[]>;
31
+ unlinkProvider(userId: string, provider: OAuthProvider): Promise<void>;
32
+ }
33
+ export interface PassportConfig {
34
+ storage: PassportStorageAdapter;
35
+ generateUsername?: (preferred?: string) => Promise<string>;
36
+ }
37
+ export declare class PassportManager {
38
+ private storage;
39
+ private generateUsername;
40
+ constructor(config: PassportConfig);
41
+ federateOAuthUser(provider: OAuthProvider, oauthUser: OAuthUser): Promise<FederationResult>;
42
+ linkProvider(userId: string, provider: OAuthProvider, oauthUser: OAuthUser): Promise<void>;
43
+ unlinkProvider(userId: string, provider: OAuthProvider): Promise<void>;
44
+ getLinkedProviders(userId: string): Promise<OAuthProvider[]>;
45
+ }
46
+ export declare function createPassportManager(config: PassportConfig): PassportManager;
47
+ //# sourceMappingURL=federation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"federation.d.ts","sourceRoot":"","sources":["../src/federation.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,CAAC;AAE7F,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,aAAa,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,sBAAsB;IACrC,YAAY,CAAC,QAAQ,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IAChG,UAAU,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACtG,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9G,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvD,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IAC5D,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxE;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,sBAAsB,CAAC;IAChC,gBAAgB,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAC5D;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,gBAAgB,CAA0C;gBAEtD,MAAM,EAAE,cAAc;IAK5B,iBAAiB,CAAC,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAoC3F,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ1F,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAQtE,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;CAGnE;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,GAAG,eAAe,CAE7E"}
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PassportManager = void 0;
4
+ exports.createPassportManager = createPassportManager;
5
+ class PassportManager {
6
+ constructor(config) {
7
+ this.storage = config.storage;
8
+ this.generateUsername = config.generateUsername ?? defaultGenerateUsername;
9
+ }
10
+ async federateOAuthUser(provider, oauthUser) {
11
+ const existing = await this.storage.findIdentity(provider, oauthUser.id);
12
+ if (existing) {
13
+ return {
14
+ userId: existing.userId,
15
+ email: oauthUser.email,
16
+ isNewUser: false,
17
+ providerLinked: true,
18
+ };
19
+ }
20
+ // Check if email already exists under a different provider
21
+ const existingUserId = await this.storage.findUserByEmail(oauthUser.email);
22
+ let userId;
23
+ let isNewUser = false;
24
+ if (existingUserId) {
25
+ userId = existingUserId;
26
+ }
27
+ else {
28
+ const username = await this.generateUsername(oauthUser.username);
29
+ userId = await this.storage.createUser({
30
+ email: oauthUser.email,
31
+ name: oauthUser.name ?? oauthUser.username ?? "User",
32
+ avatar: oauthUser.avatar,
33
+ username,
34
+ });
35
+ isNewUser = true;
36
+ }
37
+ await this.storage.linkProvider(userId, provider, oauthUser.id, oauthUser);
38
+ return { userId, email: oauthUser.email, isNewUser, providerLinked: true };
39
+ }
40
+ async linkProvider(userId, provider, oauthUser) {
41
+ const existing = await this.storage.findIdentity(provider, oauthUser.id);
42
+ if (existing && existing.userId !== userId) {
43
+ throw new Error(`This ${provider} account is already linked to a different user.`);
44
+ }
45
+ await this.storage.linkProvider(userId, provider, oauthUser.id, oauthUser);
46
+ }
47
+ async unlinkProvider(userId, provider) {
48
+ const providers = await this.storage.listUserProviders(userId);
49
+ if (providers.length <= 1) {
50
+ throw new Error("Cannot unlink the last login method. Add another provider first.");
51
+ }
52
+ await this.storage.unlinkProvider(userId, provider);
53
+ }
54
+ async getLinkedProviders(userId) {
55
+ return this.storage.listUserProviders(userId);
56
+ }
57
+ }
58
+ exports.PassportManager = PassportManager;
59
+ function createPassportManager(config) {
60
+ return new PassportManager(config);
61
+ }
62
+ async function defaultGenerateUsername(preferred) {
63
+ const base = preferred?.toLowerCase().replace(/[^a-z0-9_-]/g, "") ?? "user";
64
+ const slug = base.length >= 3 ? base : "user";
65
+ return `${slug}_${Math.random().toString(36).substring(2, 7)}`;
66
+ }
67
+ //# sourceMappingURL=federation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"federation.js","sourceRoot":"","sources":["../src/federation.ts"],"names":[],"mappings":"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"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ export { PassportManager, createPassportManager } from "./federation";
2
+ export type { OAuthProvider, OAuthUser, FederationResult, PassportStorageAdapter, PassportConfig, ProviderIdentity } from "./federation";
3
+ export { createSupabasePassportAdapter } from "./adapters/supabase";
4
+ export type { SupabaseClient } from "./adapters/supabase";
5
+ export { createMemoryPassportAdapter } from "./adapters/memory";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACtE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEzI,OAAO,EAAE,6BAA6B,EAAE,MAAM,qBAAqB,CAAC;AACpE,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC"}
package/lib/index.js ADDED
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createMemoryPassportAdapter = exports.createSupabasePassportAdapter = exports.createPassportManager = exports.PassportManager = void 0;
4
+ var federation_1 = require("./federation");
5
+ Object.defineProperty(exports, "PassportManager", { enumerable: true, get: function () { return federation_1.PassportManager; } });
6
+ Object.defineProperty(exports, "createPassportManager", { enumerable: true, get: function () { return federation_1.createPassportManager; } });
7
+ var supabase_1 = require("./adapters/supabase");
8
+ Object.defineProperty(exports, "createSupabasePassportAdapter", { enumerable: true, get: function () { return supabase_1.createSupabasePassportAdapter; } });
9
+ var memory_1 = require("./adapters/memory");
10
+ Object.defineProperty(exports, "createMemoryPassportAdapter", { enumerable: true, get: function () { return memory_1.createMemoryPassportAdapter; } });
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,2CAAsE;AAA7D,6GAAA,eAAe,OAAA;AAAE,mHAAA,qBAAqB,OAAA;AAG/C,gDAAoE;AAA3D,yHAAA,6BAA6B,OAAA;AAGtC,4CAAgE;AAAvD,qHAAA,2BAA2B,OAAA"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@aethex.os/passport",
3
+ "version": "1.0.0",
4
+ "description": "OAuth federation for multi-provider auth — link GitHub, Discord, Google, and more to a single unified user identity",
5
+ "main": "lib/index.js",
6
+ "types": "lib/index.d.ts",
7
+ "files": [
8
+ "lib"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc --watch"
13
+ },
14
+ "keywords": [
15
+ "oauth",
16
+ "federation",
17
+ "auth",
18
+ "identity",
19
+ "passport",
20
+ "social-login",
21
+ "github",
22
+ "discord",
23
+ "google",
24
+ "multi-provider",
25
+ "aethex"
26
+ ],
27
+ "author": "AeThex Corporation",
28
+ "license": "MIT",
29
+ "homepage": "https://aethex.dev",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/AeThex-Corporation/AeThex-OS.git",
33
+ "directory": "packages/aethex-passport"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^20.0.0",
37
+ "typescript": "^5.3.0"
38
+ },
39
+ "engines": {
40
+ "node": ">=18.0.0"
41
+ }
42
+ }