@yackey-labs/yauth-shared 0.1.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/index.js +39 -0
- package/package.json +35 -0
- package/src/aaguid.test.ts +50 -0
- package/src/aaguid.ts +63 -0
- package/src/index.ts +7 -0
- package/src/types.test.ts +126 -0
- package/src/types.ts +42 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/aaguid.ts
|
|
3
|
+
var AAGUID_MAP = {
|
|
4
|
+
"fbfc3007-154e-4ecc-8c0b-6e020557d7bd": "iCloud Keychain",
|
|
5
|
+
"dd4ec289-e01d-41c9-bb89-70fa845d4bf2": "iCloud Keychain (Managed)",
|
|
6
|
+
"adce0002-35bc-c60a-648b-0b25f1f05503": "Chrome on Mac",
|
|
7
|
+
"ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4": "Google Password Manager",
|
|
8
|
+
"b5397571-f314-4571-9173-2155a0baf341": "Google Password Manager",
|
|
9
|
+
"6028b017-b1d4-4c02-b4b3-afcdafc96bb2": "Windows Hello",
|
|
10
|
+
"9ddd1817-af5a-4672-a2b9-3e3dd95000a9": "Windows Hello",
|
|
11
|
+
"08987058-cadc-4b81-b6e1-30de50dcbe96": "Windows Hello",
|
|
12
|
+
"2fc0579f-8113-47ea-b116-bb5a8db9202a": "YubiKey 5 Series (USB-A, NFC)",
|
|
13
|
+
"73bb0cd4-e502-49b8-9c6f-b59445bf720b": "YubiKey 5 FIPS Series",
|
|
14
|
+
"c5ef55ff-ad9a-4b9f-b580-adebafe026d0": "YubiKey 5Ci FIPS",
|
|
15
|
+
"85203421-48f9-4355-9bc8-8a53846e5083": "YubiKey 5Ci",
|
|
16
|
+
"d8522d9f-575b-4866-88a9-ba99fa02f35b": "YubiKey Bio Series (USB-A)",
|
|
17
|
+
"ee882879-721c-4913-9775-3dfcee97617a": "YubiKey Bio Series (USB-C)",
|
|
18
|
+
"fa2b99dc-9e39-4257-8f92-4a30d23c4118": "YubiKey 5 Series (USB-C, NFC)",
|
|
19
|
+
"cb69481e-8ff7-4039-93ec-0a2729a154a8": "YubiKey 5 Series (USB-C)",
|
|
20
|
+
"c1f9a0bc-1dd2-404a-b27f-8e29047a43fd": "YubiKey 5 Series (USB-A, NFC, FIDO2)",
|
|
21
|
+
"f8a011f3-8c0a-4d15-8006-17111f9edc7d": "Security Key by Yubico (USB-A, NFC)",
|
|
22
|
+
"b92c3f9a-c014-4056-887f-140a2501163b": "Security Key by Yubico (USB-C, NFC)",
|
|
23
|
+
"6d44ba9b-f6ec-2e49-b930-0c8fe920cb73": "Security Key by Yubico (USB-C)",
|
|
24
|
+
"149a2021-8ef6-4133-96b8-81f8d5b7f1f5": "Security Key by Yubico (USB-A, FIDO2)",
|
|
25
|
+
"bada5566-a7aa-401f-bd96-45619a55120d": "1Password",
|
|
26
|
+
"b84e4048-15dc-4dd0-8640-f4f60813c8af": "1Password",
|
|
27
|
+
"d548826e-79b4-db40-a3d8-11116f7e8349": "Bitwarden",
|
|
28
|
+
"531126d6-e717-415c-9320-3d9aa6981239": "Dashlane",
|
|
29
|
+
"53414d53-554e-4700-0000-000000000000": "Samsung Pass",
|
|
30
|
+
"b267239b-954f-4041-a01b-ee4f33c145b7": "Thales IDPrime FIDO Bio",
|
|
31
|
+
"fdb141b2-5d84-443e-8a35-4698c205a502": "KeePassXC"
|
|
32
|
+
};
|
|
33
|
+
function resolveAAGUID(aaguid) {
|
|
34
|
+
return AAGUID_MAP[aaguid.toLowerCase()] ?? null;
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
resolveAAGUID,
|
|
38
|
+
AAGUID_MAP
|
|
39
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yackey-labs/yauth-shared",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "src/index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./src/index.ts",
|
|
12
|
+
"default": "./src/index.ts"
|
|
13
|
+
},
|
|
14
|
+
"./types": {
|
|
15
|
+
"import": "./src/types.ts",
|
|
16
|
+
"types": "./src/types.ts"
|
|
17
|
+
},
|
|
18
|
+
"./aaguid": {
|
|
19
|
+
"import": "./src/aaguid.ts",
|
|
20
|
+
"types": "./src/aaguid.ts"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"src"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "bun build src/index.ts --outdir dist --target bun",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"test": "bun test"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"typescript": "^5.7.2"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { AAGUID_MAP, resolveAAGUID } from "./aaguid";
|
|
3
|
+
|
|
4
|
+
describe("AAGUID_MAP", () => {
|
|
5
|
+
test("contains known Apple entry", () => {
|
|
6
|
+
expect(AAGUID_MAP["fbfc3007-154e-4ecc-8c0b-6e020557d7bd"]).toBe(
|
|
7
|
+
"iCloud Keychain",
|
|
8
|
+
);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("contains known YubiKey entry", () => {
|
|
12
|
+
expect(AAGUID_MAP["2fc0579f-8113-47ea-b116-bb5a8db9202a"]).toBe(
|
|
13
|
+
"YubiKey 5 Series (USB-A, NFC)",
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("returns undefined for unknown AAGUID", () => {
|
|
18
|
+
expect(AAGUID_MAP["00000000-0000-0000-0000-000000000000"]).toBeUndefined();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("resolveAAGUID", () => {
|
|
23
|
+
test("resolves known AAGUID", () => {
|
|
24
|
+
expect(resolveAAGUID("fbfc3007-154e-4ecc-8c0b-6e020557d7bd")).toBe(
|
|
25
|
+
"iCloud Keychain",
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("resolves case-insensitively", () => {
|
|
30
|
+
expect(resolveAAGUID("FBFC3007-154E-4ECC-8C0B-6E020557D7BD")).toBe(
|
|
31
|
+
"iCloud Keychain",
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("returns null for unknown AAGUID", () => {
|
|
36
|
+
expect(resolveAAGUID("00000000-0000-0000-0000-000000000000")).toBeNull();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("resolves 1Password entry", () => {
|
|
40
|
+
expect(resolveAAGUID("bada5566-a7aa-401f-bd96-45619a55120d")).toBe(
|
|
41
|
+
"1Password",
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("resolves Windows Hello entry", () => {
|
|
46
|
+
expect(resolveAAGUID("6028b017-b1d4-4c02-b4b3-afcdafc96bb2")).toBe(
|
|
47
|
+
"Windows Hello",
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
});
|
package/src/aaguid.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FIDO Alliance MDS AAGUID -> device name mapping.
|
|
3
|
+
* Used to resolve human-friendly names for passkey authenticators.
|
|
4
|
+
*
|
|
5
|
+
* Source: https://github.com/nickspatties/passkeys-aaguid
|
|
6
|
+
* Updated periodically. Falls back to "Unknown" for unrecognized AAGUIDs.
|
|
7
|
+
*/
|
|
8
|
+
export const AAGUID_MAP: Record<string, string> = {
|
|
9
|
+
// Apple
|
|
10
|
+
"fbfc3007-154e-4ecc-8c0b-6e020557d7bd": "iCloud Keychain",
|
|
11
|
+
"dd4ec289-e01d-41c9-bb89-70fa845d4bf2": "iCloud Keychain (Managed)",
|
|
12
|
+
|
|
13
|
+
// Google
|
|
14
|
+
"adce0002-35bc-c60a-648b-0b25f1f05503": "Chrome on Mac",
|
|
15
|
+
"ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4": "Google Password Manager",
|
|
16
|
+
"b5397571-f314-4571-9173-2155a0baf341": "Google Password Manager",
|
|
17
|
+
|
|
18
|
+
// Microsoft
|
|
19
|
+
"6028b017-b1d4-4c02-b4b3-afcdafc96bb2": "Windows Hello",
|
|
20
|
+
"9ddd1817-af5a-4672-a2b9-3e3dd95000a9": "Windows Hello",
|
|
21
|
+
"08987058-cadc-4b81-b6e1-30de50dcbe96": "Windows Hello",
|
|
22
|
+
|
|
23
|
+
// YubiKey
|
|
24
|
+
"2fc0579f-8113-47ea-b116-bb5a8db9202a": "YubiKey 5 Series (USB-A, NFC)",
|
|
25
|
+
"73bb0cd4-e502-49b8-9c6f-b59445bf720b": "YubiKey 5 FIPS Series",
|
|
26
|
+
"c5ef55ff-ad9a-4b9f-b580-adebafe026d0": "YubiKey 5Ci FIPS",
|
|
27
|
+
"85203421-48f9-4355-9bc8-8a53846e5083": "YubiKey 5Ci",
|
|
28
|
+
"d8522d9f-575b-4866-88a9-ba99fa02f35b": "YubiKey Bio Series (USB-A)",
|
|
29
|
+
"ee882879-721c-4913-9775-3dfcee97617a": "YubiKey Bio Series (USB-C)",
|
|
30
|
+
"fa2b99dc-9e39-4257-8f92-4a30d23c4118": "YubiKey 5 Series (USB-C, NFC)",
|
|
31
|
+
"cb69481e-8ff7-4039-93ec-0a2729a154a8": "YubiKey 5 Series (USB-C)",
|
|
32
|
+
"c1f9a0bc-1dd2-404a-b27f-8e29047a43fd":
|
|
33
|
+
"YubiKey 5 Series (USB-A, NFC, FIDO2)",
|
|
34
|
+
"f8a011f3-8c0a-4d15-8006-17111f9edc7d": "Security Key by Yubico (USB-A, NFC)",
|
|
35
|
+
"b92c3f9a-c014-4056-887f-140a2501163b": "Security Key by Yubico (USB-C, NFC)",
|
|
36
|
+
"6d44ba9b-f6ec-2e49-b930-0c8fe920cb73": "Security Key by Yubico (USB-C)",
|
|
37
|
+
"149a2021-8ef6-4133-96b8-81f8d5b7f1f5":
|
|
38
|
+
"Security Key by Yubico (USB-A, FIDO2)",
|
|
39
|
+
|
|
40
|
+
// 1Password
|
|
41
|
+
"bada5566-a7aa-401f-bd96-45619a55120d": "1Password",
|
|
42
|
+
"b84e4048-15dc-4dd0-8640-f4f60813c8af": "1Password",
|
|
43
|
+
|
|
44
|
+
// Bitwarden
|
|
45
|
+
"d548826e-79b4-db40-a3d8-11116f7e8349": "Bitwarden",
|
|
46
|
+
|
|
47
|
+
// Dashlane
|
|
48
|
+
"531126d6-e717-415c-9320-3d9aa6981239": "Dashlane",
|
|
49
|
+
|
|
50
|
+
// Samsung
|
|
51
|
+
"53414d53-554e-4700-0000-000000000000": "Samsung Pass",
|
|
52
|
+
|
|
53
|
+
// Thales
|
|
54
|
+
"b267239b-954f-4041-a01b-ee4f33c145b7": "Thales IDPrime FIDO Bio",
|
|
55
|
+
|
|
56
|
+
// KeePass
|
|
57
|
+
"fdb141b2-5d84-443e-8a35-4698c205a502": "KeePassXC",
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** Resolve an AAGUID to a human-friendly device name */
|
|
61
|
+
export function resolveAAGUID(aaguid: string): string | null {
|
|
62
|
+
return AAGUID_MAP[aaguid.toLowerCase()] ?? null;
|
|
63
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { AuthEvent, AuthSession, AuthUser, EventResponse } from "./types";
|
|
3
|
+
|
|
4
|
+
describe("AuthUser type", () => {
|
|
5
|
+
test("accepts valid user object", () => {
|
|
6
|
+
const user: AuthUser = {
|
|
7
|
+
id: "550e8400-e29b-41d4-a716-446655440000",
|
|
8
|
+
email: "test@example.com",
|
|
9
|
+
display_name: "Test User",
|
|
10
|
+
email_verified: true,
|
|
11
|
+
role: "user",
|
|
12
|
+
banned: false,
|
|
13
|
+
auth_method: "session",
|
|
14
|
+
};
|
|
15
|
+
expect(user.id).toBe("550e8400-e29b-41d4-a716-446655440000");
|
|
16
|
+
expect(user.role).toBe("user");
|
|
17
|
+
expect(user.auth_method).toBe("session");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("accepts null display_name", () => {
|
|
21
|
+
const user: AuthUser = {
|
|
22
|
+
id: "1",
|
|
23
|
+
email: "a@b.com",
|
|
24
|
+
display_name: null,
|
|
25
|
+
email_verified: false,
|
|
26
|
+
role: "admin",
|
|
27
|
+
banned: false,
|
|
28
|
+
auth_method: "bearer",
|
|
29
|
+
};
|
|
30
|
+
expect(user.display_name).toBeNull();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("accepts apikey auth_method", () => {
|
|
34
|
+
const user: AuthUser = {
|
|
35
|
+
id: "1",
|
|
36
|
+
email: "a@b.com",
|
|
37
|
+
display_name: null,
|
|
38
|
+
email_verified: false,
|
|
39
|
+
role: "user",
|
|
40
|
+
banned: false,
|
|
41
|
+
auth_method: "apikey",
|
|
42
|
+
};
|
|
43
|
+
expect(user.auth_method).toBe("apikey");
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("AuthSession type", () => {
|
|
48
|
+
test("accepts valid session object", () => {
|
|
49
|
+
const session: AuthSession = {
|
|
50
|
+
id: "session-1",
|
|
51
|
+
user_id: "user-1",
|
|
52
|
+
expires_at: "2025-12-31T23:59:59Z",
|
|
53
|
+
ip_address: "192.168.1.1",
|
|
54
|
+
user_agent: "Mozilla/5.0",
|
|
55
|
+
};
|
|
56
|
+
expect(session.id).toBe("session-1");
|
|
57
|
+
expect(session.ip_address).toBe("192.168.1.1");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("accepts null ip_address and user_agent", () => {
|
|
61
|
+
const session: AuthSession = {
|
|
62
|
+
id: "s",
|
|
63
|
+
user_id: "u",
|
|
64
|
+
expires_at: "2025-01-01T00:00:00Z",
|
|
65
|
+
ip_address: null,
|
|
66
|
+
user_agent: null,
|
|
67
|
+
};
|
|
68
|
+
expect(session.ip_address).toBeNull();
|
|
69
|
+
expect(session.user_agent).toBeNull();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("AuthEvent type", () => {
|
|
74
|
+
test("userRegistered event has correct shape", () => {
|
|
75
|
+
const event: AuthEvent = {
|
|
76
|
+
type: "userRegistered",
|
|
77
|
+
userId: "user-1",
|
|
78
|
+
email: "test@example.com",
|
|
79
|
+
};
|
|
80
|
+
expect(event.type).toBe("userRegistered");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("loginFailed event includes reason", () => {
|
|
84
|
+
const event: AuthEvent = {
|
|
85
|
+
type: "loginFailed",
|
|
86
|
+
email: "test@example.com",
|
|
87
|
+
method: "email",
|
|
88
|
+
reason: "invalid password",
|
|
89
|
+
};
|
|
90
|
+
expect(event.reason).toBe("invalid password");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("magicLinkVerified event includes isNewUser", () => {
|
|
94
|
+
const event: AuthEvent = {
|
|
95
|
+
type: "magicLinkVerified",
|
|
96
|
+
userId: "user-1",
|
|
97
|
+
isNewUser: true,
|
|
98
|
+
};
|
|
99
|
+
expect(event.isNewUser).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("EventResponse type", () => {
|
|
104
|
+
test("continue response", () => {
|
|
105
|
+
const resp: EventResponse = { action: "continue" };
|
|
106
|
+
expect(resp.action).toBe("continue");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("requireMfa response", () => {
|
|
110
|
+
const resp: EventResponse = {
|
|
111
|
+
action: "requireMfa",
|
|
112
|
+
userId: "user-1",
|
|
113
|
+
pendingSessionId: "session-1",
|
|
114
|
+
};
|
|
115
|
+
expect(resp.action).toBe("requireMfa");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("block response", () => {
|
|
119
|
+
const resp: EventResponse = {
|
|
120
|
+
action: "block",
|
|
121
|
+
status: 403,
|
|
122
|
+
message: "Account banned",
|
|
123
|
+
};
|
|
124
|
+
expect(resp.status).toBe(403);
|
|
125
|
+
});
|
|
126
|
+
});
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** Auth event types emitted by plugins for cross-plugin communication */
|
|
2
|
+
export type AuthEvent =
|
|
3
|
+
| { type: "userRegistered"; userId: string; email: string }
|
|
4
|
+
| { type: "loginSucceeded"; userId: string; method: string }
|
|
5
|
+
| { type: "loginFailed"; email: string; method: string; reason: string }
|
|
6
|
+
| { type: "sessionCreated"; userId: string; sessionId: string }
|
|
7
|
+
| { type: "logout"; userId: string; sessionId: string }
|
|
8
|
+
| { type: "passwordChanged"; userId: string }
|
|
9
|
+
| { type: "emailVerified"; userId: string }
|
|
10
|
+
| { type: "mfaEnabled"; userId: string; method: string }
|
|
11
|
+
| { type: "mfaDisabled"; userId: string; method: string }
|
|
12
|
+
| { type: "userBanned"; userId: string }
|
|
13
|
+
| { type: "userUnbanned"; userId: string }
|
|
14
|
+
| { type: "magicLinkSent"; email: string }
|
|
15
|
+
| { type: "magicLinkVerified"; userId: string; isNewUser: boolean };
|
|
16
|
+
|
|
17
|
+
/** Response from an event handler — controls auth flow */
|
|
18
|
+
export type EventResponse =
|
|
19
|
+
| { action: "continue" }
|
|
20
|
+
| { action: "requireMfa"; userId: string; pendingSessionId: string }
|
|
21
|
+
| { action: "block"; status: number; message: string };
|
|
22
|
+
|
|
23
|
+
/** Authenticated user returned from the API (snake_case matches Rust serde) */
|
|
24
|
+
export interface AuthUser {
|
|
25
|
+
id: string;
|
|
26
|
+
email: string;
|
|
27
|
+
display_name: string | null;
|
|
28
|
+
email_verified: boolean;
|
|
29
|
+
role: "user" | "admin";
|
|
30
|
+
banned: boolean;
|
|
31
|
+
/** Which method was used for this request: 'session' | 'bearer' | 'apikey' */
|
|
32
|
+
auth_method: "session" | "bearer" | "apikey";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Session info for cookie-based auth (snake_case matches Rust serde) */
|
|
36
|
+
export interface AuthSession {
|
|
37
|
+
id: string;
|
|
38
|
+
user_id: string;
|
|
39
|
+
expires_at: string;
|
|
40
|
+
ip_address: string | null;
|
|
41
|
+
user_agent: string | null;
|
|
42
|
+
}
|