autherr 1.0.11 → 2.0.2
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/crypto/createClientAssertion.d.ts +1 -0
- package/dist/crypto/createClientAssertion.js +17 -0
- package/dist/crypto/pemUtils.d.ts +1 -0
- package/dist/crypto/pemUtils.js +13 -0
- package/dist/provider/AutherrProvider.d.ts +2 -1
- package/dist/provider/AutherrProvider.js +15 -3
- package/package.json +2 -1
- package/src/crypto/createClientAssertion.ts +29 -0
- package/src/crypto/pemUtils.ts +16 -0
- package/src/provider/AutherrProvider.tsx +31 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createClientAssertion(clientId: string, privateKeyPem: string): Promise<string>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/crypto/createClientAssertion.ts
|
|
2
|
+
import { SignJWT } from "jose";
|
|
3
|
+
import { pemToArrayBuffer } from "./pemUtils";
|
|
4
|
+
export async function createClientAssertion(clientId, privateKeyPem) {
|
|
5
|
+
const key = await crypto.subtle.importKey("pkcs8", pemToArrayBuffer(privateKeyPem), {
|
|
6
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
7
|
+
hash: "SHA-256",
|
|
8
|
+
}, false, ["sign"]);
|
|
9
|
+
const now = Math.floor(Date.now() / 1000);
|
|
10
|
+
return await new SignJWT({ clientId })
|
|
11
|
+
.setProtectedHeader({ alg: "RS256", typ: "JWT" })
|
|
12
|
+
.setIssuedAt(now)
|
|
13
|
+
.setExpirationTime(now + 120) // ⏱ 2 minutes
|
|
14
|
+
.setAudience("autherr")
|
|
15
|
+
.setIssuer(clientId)
|
|
16
|
+
.sign(key);
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function pemToArrayBuffer(pem: string): ArrayBuffer;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// src/crypto/pemUtils.ts
|
|
2
|
+
export function pemToArrayBuffer(pem) {
|
|
3
|
+
const b64 = pem
|
|
4
|
+
.replace(/-----BEGIN [^-]+-----/, "")
|
|
5
|
+
.replace(/-----END [^-]+-----/, "")
|
|
6
|
+
.replace(/\s+/g, "");
|
|
7
|
+
const binary = atob(b64);
|
|
8
|
+
const bytes = new Uint8Array(binary.length);
|
|
9
|
+
for (let i = 0; i < binary.length; i++) {
|
|
10
|
+
bytes[i] = binary.charCodeAt(i);
|
|
11
|
+
}
|
|
12
|
+
return bytes.buffer;
|
|
13
|
+
}
|
|
@@ -12,7 +12,8 @@ interface AutherrProviderProps {
|
|
|
12
12
|
children: React.ReactNode;
|
|
13
13
|
clientId: string;
|
|
14
14
|
baseUrl: string;
|
|
15
|
+
clientPrivateKey: string;
|
|
15
16
|
}
|
|
16
|
-
export declare function AutherrProvider({ children, clientId, baseUrl, }: AutherrProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
export declare function AutherrProvider({ children, clientId, baseUrl, clientPrivateKey, }: AutherrProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
17
18
|
export declare function useAutherrContext(): AutherrContextValue;
|
|
18
19
|
export {};
|
|
@@ -2,8 +2,16 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { createContext, useContext, useEffect, useMemo, useState, } from "react";
|
|
3
3
|
import { fetchSession } from "../api/session";
|
|
4
4
|
import { logoutSession } from "../api/logout";
|
|
5
|
+
import { createClientAssertion } from "../crypto/createClientAssertion";
|
|
6
|
+
function setClientAssertionCookie(token) {
|
|
7
|
+
document.cookie =
|
|
8
|
+
`autherr_client_assertion=${token};` +
|
|
9
|
+
`Path=/;` +
|
|
10
|
+
`Secure;` +
|
|
11
|
+
`SameSite=None`;
|
|
12
|
+
}
|
|
5
13
|
const AutherrContext = createContext(null);
|
|
6
|
-
export function AutherrProvider({ children, clientId, baseUrl, }) {
|
|
14
|
+
export function AutherrProvider({ children, clientId, baseUrl, clientPrivateKey, }) {
|
|
7
15
|
const [accessToken, setAccessToken] = useState(null);
|
|
8
16
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
9
17
|
const refreshSession = async () => {
|
|
@@ -20,16 +28,20 @@ export function AutherrProvider({ children, clientId, baseUrl, }) {
|
|
|
20
28
|
useEffect(() => {
|
|
21
29
|
refreshSession();
|
|
22
30
|
}, [baseUrl, clientId]);
|
|
23
|
-
const login = () => {
|
|
31
|
+
const login = async () => {
|
|
24
32
|
const state = crypto.randomUUID();
|
|
33
|
+
const assertion = await createClientAssertion(clientId, clientPrivateKey);
|
|
34
|
+
setClientAssertionCookie(assertion);
|
|
25
35
|
window.location.href =
|
|
26
36
|
`${baseUrl}/auth/login` +
|
|
27
37
|
`?client_id=${clientId}` +
|
|
28
38
|
`&redirect_uri=${encodeURIComponent(window.location.origin)}` +
|
|
29
39
|
`&state=${state}`;
|
|
30
40
|
};
|
|
31
|
-
const signup = () => {
|
|
41
|
+
const signup = async () => {
|
|
32
42
|
const state = crypto.randomUUID();
|
|
43
|
+
const assertion = await createClientAssertion(clientId, clientPrivateKey);
|
|
44
|
+
setClientAssertionCookie(assertion);
|
|
33
45
|
window.location.href =
|
|
34
46
|
`${baseUrl}/auth/signup` +
|
|
35
47
|
`?client_id=${clientId}` +
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autherr",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"dest": "dist",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "tsc -p tsconfig.json"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
+
"jose": "^6.1.3",
|
|
10
11
|
"react": "^19.2.3"
|
|
11
12
|
},
|
|
12
13
|
"devDependencies": {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// src/crypto/createClientAssertion.ts
|
|
2
|
+
import { SignJWT } from "jose";
|
|
3
|
+
import { pemToArrayBuffer } from "./pemUtils";
|
|
4
|
+
|
|
5
|
+
export async function createClientAssertion(
|
|
6
|
+
clientId: string,
|
|
7
|
+
privateKeyPem: string
|
|
8
|
+
): Promise<string> {
|
|
9
|
+
const key = await crypto.subtle.importKey(
|
|
10
|
+
"pkcs8",
|
|
11
|
+
pemToArrayBuffer(privateKeyPem),
|
|
12
|
+
{
|
|
13
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
14
|
+
hash: "SHA-256",
|
|
15
|
+
},
|
|
16
|
+
false,
|
|
17
|
+
["sign"]
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const now = Math.floor(Date.now() / 1000);
|
|
21
|
+
|
|
22
|
+
return await new SignJWT({ clientId })
|
|
23
|
+
.setProtectedHeader({ alg: "RS256", typ: "JWT" })
|
|
24
|
+
.setIssuedAt(now)
|
|
25
|
+
.setExpirationTime(now + 120) // ⏱ 2 minutes
|
|
26
|
+
.setAudience("autherr")
|
|
27
|
+
.setIssuer(clientId)
|
|
28
|
+
.sign(key);
|
|
29
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// src/crypto/pemUtils.ts
|
|
2
|
+
export function pemToArrayBuffer(pem: string): ArrayBuffer {
|
|
3
|
+
const b64 = pem
|
|
4
|
+
.replace(/-----BEGIN [^-]+-----/, "")
|
|
5
|
+
.replace(/-----END [^-]+-----/, "")
|
|
6
|
+
.replace(/\s+/g, "");
|
|
7
|
+
|
|
8
|
+
const binary = atob(b64);
|
|
9
|
+
const bytes = new Uint8Array(binary.length);
|
|
10
|
+
|
|
11
|
+
for (let i = 0; i < binary.length; i++) {
|
|
12
|
+
bytes[i] = binary.charCodeAt(i);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return bytes.buffer;
|
|
16
|
+
}
|
|
@@ -8,6 +8,7 @@ import React, {
|
|
|
8
8
|
import { fetchSession } from "../api/session";
|
|
9
9
|
import { logoutSession } from "../api/logout";
|
|
10
10
|
import { AutherrUser } from "../types/auth";
|
|
11
|
+
import { createClientAssertion } from "../crypto/createClientAssertion";
|
|
11
12
|
|
|
12
13
|
interface AutherrContextValue {
|
|
13
14
|
accessToken: string | null;
|
|
@@ -19,6 +20,15 @@ interface AutherrContextValue {
|
|
|
19
20
|
refreshSession: () => Promise<void>;
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
function setClientAssertionCookie(token: string) {
|
|
24
|
+
document.cookie =
|
|
25
|
+
`autherr_client_assertion=${token};` +
|
|
26
|
+
`Path=/;` +
|
|
27
|
+
`Secure;` +
|
|
28
|
+
`SameSite=None`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
22
32
|
|
|
23
33
|
const AutherrContext = createContext<AutherrContextValue | null>(null);
|
|
24
34
|
|
|
@@ -26,12 +36,14 @@ interface AutherrProviderProps {
|
|
|
26
36
|
children: React.ReactNode;
|
|
27
37
|
clientId: string;
|
|
28
38
|
baseUrl: string; // e.g. https://autherr.com
|
|
39
|
+
clientPrivateKey: string;
|
|
29
40
|
}
|
|
30
41
|
|
|
31
42
|
export function AutherrProvider({
|
|
32
43
|
children,
|
|
33
44
|
clientId,
|
|
34
45
|
baseUrl,
|
|
46
|
+
clientPrivateKey,
|
|
35
47
|
}: AutherrProviderProps) {
|
|
36
48
|
|
|
37
49
|
const [accessToken, setAccessToken] = useState<string | null>(null);
|
|
@@ -54,8 +66,16 @@ export function AutherrProvider({
|
|
|
54
66
|
refreshSession();
|
|
55
67
|
}, [baseUrl, clientId]);
|
|
56
68
|
|
|
57
|
-
const login = () => {
|
|
69
|
+
const login = async () => {
|
|
58
70
|
const state = crypto.randomUUID();
|
|
71
|
+
|
|
72
|
+
const assertion = await createClientAssertion(
|
|
73
|
+
clientId,
|
|
74
|
+
clientPrivateKey
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
setClientAssertionCookie(assertion);
|
|
78
|
+
|
|
59
79
|
window.location.href =
|
|
60
80
|
`${baseUrl}/auth/login` +
|
|
61
81
|
`?client_id=${clientId}` +
|
|
@@ -63,8 +83,16 @@ export function AutherrProvider({
|
|
|
63
83
|
`&state=${state}`;
|
|
64
84
|
};
|
|
65
85
|
|
|
66
|
-
const signup = () => {
|
|
86
|
+
const signup = async () => {
|
|
67
87
|
const state = crypto.randomUUID();
|
|
88
|
+
|
|
89
|
+
const assertion = await createClientAssertion(
|
|
90
|
+
clientId,
|
|
91
|
+
clientPrivateKey
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
setClientAssertionCookie(assertion);
|
|
95
|
+
|
|
68
96
|
window.location.href =
|
|
69
97
|
`${baseUrl}/auth/signup` +
|
|
70
98
|
`?client_id=${clientId}` +
|
|
@@ -72,6 +100,7 @@ export function AutherrProvider({
|
|
|
72
100
|
`&state=${state}`;
|
|
73
101
|
};
|
|
74
102
|
|
|
103
|
+
|
|
75
104
|
const logout = async () => {
|
|
76
105
|
try {
|
|
77
106
|
await logoutSession(baseUrl, clientId);
|