autherr 2.0.1 → 2.0.3

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.
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "autherr",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "dest": "dist",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -17,13 +17,13 @@ export async function createClientAssertion(
17
17
  ["sign"]
18
18
  );
19
19
 
20
- const now = Math.floor(Date.now() / 1000);
21
-
22
- return await new SignJWT({ clientId })
20
+ return await new SignJWT({})
23
21
  .setProtectedHeader({ alg: "RS256", typ: "JWT" })
24
- .setIssuedAt(now)
25
- .setExpirationTime(now + 120) // ⏱ 2 minutes
26
- .setAudience("autherr")
27
- .setIssuer(clientId)
22
+ .setIssuer(clientId) // iss
23
+ .setSubject(clientId) // sub
24
+ .setAudience("autherr") // aud
25
+ .setIssuedAt() // iat = now
26
+ .setExpirationTime("2m") // exp = iat + 2 min
27
+ .setJti(crypto.randomUUID()) // anti-replay
28
28
  .sign(key);
29
29
  }
@@ -7,35 +7,24 @@ import React, {
7
7
  } from "react";
8
8
  import { fetchSession } from "../api/session";
9
9
  import { logoutSession } from "../api/logout";
10
- import { AutherrUser } from "../types/auth";
11
10
  import { createClientAssertion } from "../crypto/createClientAssertion";
12
11
 
13
12
  interface AutherrContextValue {
14
13
  accessToken: string | null;
15
14
  isAuthenticated: boolean;
16
- login: () => void;
17
- signup: () => void;
15
+ login: () => Promise<void>;
16
+ signup: () => Promise<void>;
18
17
  logout: () => Promise<void>;
19
18
  getAccessToken: () => string | null;
20
19
  refreshSession: () => Promise<void>;
21
20
  }
22
21
 
23
- function setClientAssertionCookie(token: string) {
24
- document.cookie =
25
- `autherr_client_assertion=${token};` +
26
- `Path=/;` +
27
- `Secure;` +
28
- `SameSite=None`;
29
- }
30
-
31
-
32
-
33
22
  const AutherrContext = createContext<AutherrContextValue | null>(null);
34
23
 
35
24
  interface AutherrProviderProps {
36
25
  children: React.ReactNode;
37
26
  clientId: string;
38
- baseUrl: string; // e.g. https://autherr.com
27
+ baseUrl: string;
39
28
  clientPrivateKey: string;
40
29
  }
41
30
 
@@ -45,13 +34,11 @@ export function AutherrProvider({
45
34
  baseUrl,
46
35
  clientPrivateKey,
47
36
  }: AutherrProviderProps) {
48
-
49
37
  const [accessToken, setAccessToken] = useState<string | null>(null);
50
38
  const [isAuthenticated, setIsAuthenticated] = useState(false);
51
39
 
52
40
  const refreshSession = async () => {
53
41
  const session = await fetchSession(baseUrl, clientId);
54
-
55
42
  if (session.authenticated && session.accessToken) {
56
43
  setAccessToken(session.accessToken);
57
44
  setIsAuthenticated(true);
@@ -61,12 +48,11 @@ export function AutherrProvider({
61
48
  }
62
49
  };
63
50
 
64
-
65
51
  useEffect(() => {
66
52
  refreshSession();
67
53
  }, [baseUrl, clientId]);
68
54
 
69
- const login = async () => {
55
+ const buildRedirect = async (path: "login" | "signup") => {
70
56
  const state = crypto.randomUUID();
71
57
 
72
58
  const assertion = await createClientAssertion(
@@ -74,41 +60,34 @@ export function AutherrProvider({
74
60
  clientPrivateKey
75
61
  );
76
62
 
77
- setClientAssertionCookie(assertion);
78
-
79
- window.location.href =
80
- `${baseUrl}/auth/login` +
81
- `?client_id=${clientId}` +
82
- `&redirect_uri=${encodeURIComponent(window.location.origin)}` +
83
- `&state=${state}`;
84
- };
85
-
86
- const signup = async () => {
87
- const state = crypto.randomUUID();
88
-
89
- const assertion = await createClientAssertion(
90
- clientId,
91
- clientPrivateKey
63
+ const url = new URL(`${baseUrl}/auth/${path}`);
64
+ url.searchParams.set("client_id", clientId);
65
+ url.searchParams.set(
66
+ "redirect_uri",
67
+ window.location.origin
92
68
  );
93
-
94
- setClientAssertionCookie(assertion);
95
-
96
- window.location.href =
97
- `${baseUrl}/auth/signup` +
98
- `?client_id=${clientId}` +
99
- `&redirect_uri=${encodeURIComponent(window.location.origin)}` +
100
- `&state=${state}`;
69
+ url.searchParams.set("state", state);
70
+
71
+ // Attach assertion as header via fetch redirect
72
+ await fetch(url.toString(), {
73
+ method: "GET",
74
+ headers: {
75
+ "X-Client-Assertion": assertion,
76
+ },
77
+ credentials: "include",
78
+ }).then((res) => {
79
+ window.location.href = res.url;
80
+ });
101
81
  };
102
82
 
83
+ const login = () => buildRedirect("login");
84
+ const signup = () => buildRedirect("signup");
103
85
 
104
86
  const logout = async () => {
105
- try {
106
- await logoutSession(baseUrl, clientId);
107
- } finally {
108
- setAccessToken(null);
109
- setIsAuthenticated(false);
110
- window.location.href = window.location.origin;
111
- }
87
+ await logoutSession(baseUrl, clientId);
88
+ setAccessToken(null);
89
+ setIsAuthenticated(false);
90
+ window.location.href = window.location.origin;
112
91
  };
113
92
 
114
93
  const value = useMemo(
@@ -124,7 +103,6 @@ export function AutherrProvider({
124
103
  [accessToken, isAuthenticated]
125
104
  );
126
105
 
127
-
128
106
  return (
129
107
  <AutherrContext.Provider value={value}>
130
108
  {children}