@companyio/auth-client 0.1.3 → 0.1.5
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 +23 -0
- package/package.json +13 -3
- package/src/index.ts +0 -184
- package/tsconfig.json +0 -11
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# @companyio/auth-client
|
|
2
|
+
|
|
3
|
+
Platform-neutral client for password sign-in, signup, session restoration, logout, current-user requests, and organization lookup.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { AuthClient, createBrowserStorage } from '@companyio/auth-client';
|
|
9
|
+
|
|
10
|
+
const authClient = new AuthClient({
|
|
11
|
+
baseUrl: import.meta.env.VITE_API_URL ?? 'http://localhost:3000',
|
|
12
|
+
clientId: 'my-app',
|
|
13
|
+
storage: createBrowserStorage(),
|
|
14
|
+
});
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Use `createMemoryStorage()` for tests or short-lived sessions. The client expects the API under `/api/v1` and sends bearer tokens automatically.
|
|
18
|
+
|
|
19
|
+
## Build
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm --filter @companyio/auth-client build
|
|
23
|
+
```
|
package/package.json
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@companyio/auth-client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Platform-neutral authentication and user-management client",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
8
18
|
"dependencies": {
|
|
9
|
-
"@companyio/auth-contracts": "0.1.
|
|
19
|
+
"@companyio/auth-contracts": "0.1.5"
|
|
10
20
|
},
|
|
11
21
|
"devDependencies": {
|
|
12
22
|
"typescript": "^5.3.3"
|
|
13
23
|
},
|
|
14
24
|
"scripts": {
|
|
15
25
|
"build": "tsc",
|
|
16
|
-
"test": "
|
|
26
|
+
"test": "vitest run"
|
|
17
27
|
}
|
|
18
28
|
}
|
package/src/index.ts
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
AuthClientConfig,
|
|
3
|
-
AuthError,
|
|
4
|
-
AuthSession,
|
|
5
|
-
Organization,
|
|
6
|
-
SignInInput,
|
|
7
|
-
SignUpInput,
|
|
8
|
-
SessionSchema,
|
|
9
|
-
TokenStorage,
|
|
10
|
-
User,
|
|
11
|
-
UserSchema,
|
|
12
|
-
} from '@companyio/auth-contracts';
|
|
13
|
-
|
|
14
|
-
const memoryStorage = (): TokenStorage => {
|
|
15
|
-
let token: string | null = null;
|
|
16
|
-
|
|
17
|
-
return {
|
|
18
|
-
get: async () => token,
|
|
19
|
-
set: async (value) => {
|
|
20
|
-
token = value;
|
|
21
|
-
},
|
|
22
|
-
clear: async () => {
|
|
23
|
-
token = null;
|
|
24
|
-
},
|
|
25
|
-
};
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export const createMemoryStorage = memoryStorage;
|
|
29
|
-
|
|
30
|
-
export class AuthClient {
|
|
31
|
-
private readonly baseUrl: string;
|
|
32
|
-
private readonly clientId: string;
|
|
33
|
-
private readonly storage: TokenStorage;
|
|
34
|
-
private readonly requestFetch: typeof fetch;
|
|
35
|
-
private sessionValue: AuthSession | null = null;
|
|
36
|
-
private listeners = new Set<(session: AuthSession | null) => void>();
|
|
37
|
-
|
|
38
|
-
constructor(config: AuthClientConfig) {
|
|
39
|
-
const configuredBaseUrl = config.baseUrl.replace(/\/$/, '');
|
|
40
|
-
this.baseUrl = configuredBaseUrl.endsWith('/api/v1')
|
|
41
|
-
? configuredBaseUrl
|
|
42
|
-
: `${configuredBaseUrl}/api/v1`;
|
|
43
|
-
this.clientId = config.clientId;
|
|
44
|
-
this.storage = config.storage ?? memoryStorage();
|
|
45
|
-
this.requestFetch = (config.fetch ?? globalThis.fetch).bind(globalThis);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
get session(): AuthSession | null {
|
|
49
|
-
return this.sessionValue;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
subscribe(listener: (session: AuthSession | null) => void): () => void {
|
|
53
|
-
this.listeners.add(listener);
|
|
54
|
-
return () => this.listeners.delete(listener);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async restore(): Promise<AuthSession | null> {
|
|
58
|
-
const token = await this.storage.get();
|
|
59
|
-
if (!token) return null;
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
const session = await this.request<AuthSession>('/auth/session', { method: 'GET' });
|
|
63
|
-
this.setSession(SessionSchema.parse(session));
|
|
64
|
-
return this.sessionValue;
|
|
65
|
-
} catch {
|
|
66
|
-
await this.signOut();
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async signIn(provider = 'default'): Promise<void> {
|
|
72
|
-
const response = await this.request<{ authorizationUrl: string }>('/auth/authorize', {
|
|
73
|
-
method: 'POST',
|
|
74
|
-
body: JSON.stringify({ clientId: this.clientId, provider }),
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
if (typeof window !== 'undefined') {
|
|
78
|
-
window.location.assign(response.authorizationUrl);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async signInWithPassword(input: SignInInput): Promise<AuthSession> {
|
|
83
|
-
const session = SessionSchema.parse(
|
|
84
|
-
await this.request<AuthSession>('/auth/sign-in', {
|
|
85
|
-
method: 'POST',
|
|
86
|
-
body: JSON.stringify(input),
|
|
87
|
-
})
|
|
88
|
-
);
|
|
89
|
-
this.setSession(session);
|
|
90
|
-
return session;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async signUp(input: SignUpInput): Promise<AuthSession> {
|
|
94
|
-
const session = SessionSchema.parse(
|
|
95
|
-
await this.request<AuthSession>('/auth/sign-up', {
|
|
96
|
-
method: 'POST',
|
|
97
|
-
body: JSON.stringify(input),
|
|
98
|
-
})
|
|
99
|
-
);
|
|
100
|
-
this.setSession(session);
|
|
101
|
-
return session;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async completeSignIn(code: string, redirectUri: string): Promise<AuthSession> {
|
|
105
|
-
const session = SessionSchema.parse(
|
|
106
|
-
await this.request<AuthSession>('/auth/callback', {
|
|
107
|
-
method: 'POST',
|
|
108
|
-
body: JSON.stringify({ clientId: this.clientId, code, redirectUri }),
|
|
109
|
-
})
|
|
110
|
-
);
|
|
111
|
-
this.setSession(session);
|
|
112
|
-
return session;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async getCurrentUser(): Promise<User> {
|
|
116
|
-
return UserSchema.parse(await this.request<User>('/users/me'));
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
async getOrganizations(): Promise<Organization[]> {
|
|
120
|
-
return this.request<Organization[]>('/organizations');
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async signOut(): Promise<void> {
|
|
124
|
-
try {
|
|
125
|
-
await this.request('/auth/logout', { method: 'POST' });
|
|
126
|
-
} finally {
|
|
127
|
-
await this.storage.clear();
|
|
128
|
-
this.setSession(null);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
private setSession(session: AuthSession | null): void {
|
|
133
|
-
this.sessionValue = session;
|
|
134
|
-
session ? void this.storage.set(session.accessToken) : void this.storage.clear();
|
|
135
|
-
this.listeners.forEach((listener) => listener(session));
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
private async request<T>(path: string, init: RequestInit = {}): Promise<T> {
|
|
139
|
-
const token = await this.storage.get();
|
|
140
|
-
let response: Response;
|
|
141
|
-
|
|
142
|
-
try {
|
|
143
|
-
response = await this.requestFetch(`${this.baseUrl}${path}`, {
|
|
144
|
-
...init,
|
|
145
|
-
headers: {
|
|
146
|
-
'Content-Type': 'application/json',
|
|
147
|
-
'X-Client-Id': this.clientId,
|
|
148
|
-
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
149
|
-
...init.headers,
|
|
150
|
-
},
|
|
151
|
-
});
|
|
152
|
-
} catch {
|
|
153
|
-
throw {
|
|
154
|
-
code: 'NETWORK_ERROR',
|
|
155
|
-
message: `Unable to reach the API at ${this.baseUrl}. Start the API with "pnpm --filter @companyio/api dev" and try again.`,
|
|
156
|
-
} satisfies AuthError;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (!response.ok) {
|
|
160
|
-
throw await this.toAuthError(response);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (response.status === 204) return undefined as T;
|
|
164
|
-
return (await response.json()) as T;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
private async toAuthError(response: Response): Promise<AuthError> {
|
|
168
|
-
const body = (await response.json().catch(() => null)) as { message?: string } | null;
|
|
169
|
-
const code: AuthError['code'] =
|
|
170
|
-
response.status === 401
|
|
171
|
-
? 'UNAUTHENTICATED'
|
|
172
|
-
: response.status === 403
|
|
173
|
-
? 'FORBIDDEN'
|
|
174
|
-
: response.status < 500
|
|
175
|
-
? 'INVALID_REQUEST'
|
|
176
|
-
: 'UNKNOWN';
|
|
177
|
-
|
|
178
|
-
return {
|
|
179
|
-
code,
|
|
180
|
-
message: body?.message ?? 'Authentication request failed.',
|
|
181
|
-
status: response.status,
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
}
|