local-lite 0.0.12 → 0.0.14

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,284 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GitHubAuthClient = exports.DEFAULT_GITHUB_AUTH_SCOPES = exports.GITHUB_API_ENDPOINT = exports.GITHUB_TOKEN_ENDPOINT = exports.GITHUB_AUTHORIZATION_ENDPOINT = void 0;
4
+ exports.createInMemoryGitHubTokenStore = createInMemoryGitHubTokenStore;
5
+ exports.createGitHubAuthTokenStore = createGitHubAuthTokenStore;
6
+ exports.GITHUB_AUTHORIZATION_ENDPOINT = 'https://github.com/login/oauth/authorize';
7
+ exports.GITHUB_TOKEN_ENDPOINT = 'https://github.com/login/oauth/access_token';
8
+ exports.GITHUB_API_ENDPOINT = 'https://api.github.com';
9
+ exports.DEFAULT_GITHUB_AUTH_SCOPES = ['read:user', 'user:email'];
10
+ function secureRandomBytes(length) {
11
+ const crypto = globalThis.crypto;
12
+ if (!crypto?.getRandomValues) {
13
+ throw new Error('Secure random number generation is unavailable.');
14
+ }
15
+ return crypto.getRandomValues(new Uint8Array(length));
16
+ }
17
+ function base64UrlEncode(bytes) {
18
+ const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
19
+ let result = '';
20
+ for (let index = 0; index < bytes.length; index += 3) {
21
+ const first = bytes[index];
22
+ const second = bytes[index + 1];
23
+ const third = bytes[index + 2];
24
+ result += alphabet[first >> 2];
25
+ result += alphabet[((first & 3) << 4) | (second === undefined ? 0 : second >> 4)];
26
+ if (second !== undefined) {
27
+ result += alphabet[((second & 15) << 2) | (third === undefined ? 0 : third >> 6)];
28
+ }
29
+ if (third !== undefined)
30
+ result += alphabet[third & 63];
31
+ }
32
+ return result;
33
+ }
34
+ function validateTokens(value) {
35
+ if (!value || typeof value !== 'object')
36
+ return false;
37
+ const tokens = value;
38
+ return typeof tokens.accessToken === 'string';
39
+ }
40
+ function validateUser(value) {
41
+ if (!value || typeof value !== 'object')
42
+ return false;
43
+ const user = value;
44
+ return (typeof user.id === 'string' &&
45
+ typeof user.login === 'string' &&
46
+ typeof user.name === 'string');
47
+ }
48
+ function createInMemoryGitHubTokenStore() {
49
+ let storedTokens = null;
50
+ let storedUser = null;
51
+ return {
52
+ getTokens: async () => storedTokens,
53
+ setTokens: async (tokens) => {
54
+ storedTokens = tokens;
55
+ },
56
+ removeTokens: async () => {
57
+ storedTokens = null;
58
+ },
59
+ getUser: async () => storedUser,
60
+ setUser: async (user) => {
61
+ storedUser = user;
62
+ },
63
+ };
64
+ }
65
+ function createGitHubAuthTokenStore(storage, prefix = 'local-lite-github-auth') {
66
+ const tokensKey = `${prefix}-tokens`;
67
+ const userKey = `${prefix}-user`;
68
+ return {
69
+ getTokens: async () => {
70
+ const raw = await storage.getItem(tokensKey);
71
+ return validateTokens(raw) ? raw : null;
72
+ },
73
+ setTokens: tokens => storage.setItem(tokensKey, tokens),
74
+ removeTokens: () => storage.removeItem(tokensKey),
75
+ getUser: async () => {
76
+ const raw = await storage.getItem(userKey);
77
+ return validateUser(raw) ? raw : null;
78
+ },
79
+ setUser: async (user) => {
80
+ if (user) {
81
+ await storage.setItem(userKey, user);
82
+ }
83
+ else {
84
+ await storage.removeItem(userKey);
85
+ }
86
+ },
87
+ };
88
+ }
89
+ class GitHubAuthClient {
90
+ options;
91
+ tokenStore;
92
+ fetcher;
93
+ listeners = new Set();
94
+ currentUser = null;
95
+ constructor(options) {
96
+ this.options = options;
97
+ if (!options.clientId?.trim()) {
98
+ throw new Error('GitHub OAuth client ID is required.');
99
+ }
100
+ if (!options.redirectUri?.trim()) {
101
+ throw new Error('GitHub OAuth redirect URI is required.');
102
+ }
103
+ this.fetcher = options.fetch ?? fetch;
104
+ if (options.tokenStore) {
105
+ this.tokenStore = options.tokenStore;
106
+ }
107
+ else if (options.storage) {
108
+ this.tokenStore = createGitHubAuthTokenStore(options.storage);
109
+ }
110
+ else {
111
+ this.tokenStore = createInMemoryGitHubTokenStore();
112
+ }
113
+ }
114
+ onAuthStateChange(listener) {
115
+ this.listeners.add(listener);
116
+ return () => {
117
+ this.listeners.delete(listener);
118
+ };
119
+ }
120
+ notify(user) {
121
+ this.currentUser = user;
122
+ for (const listener of this.listeners) {
123
+ try {
124
+ listener(user);
125
+ }
126
+ catch (err) {
127
+ console.error('Error in GitHubAuthClient listener:', err);
128
+ }
129
+ }
130
+ }
131
+ async signIn() {
132
+ const randomBytes = this.options.randomBytes ?? secureRandomBytes;
133
+ const stateBytes = randomBytes(32);
134
+ if (stateBytes.length !== 32) {
135
+ throw new Error('The randomBytes provider returned an invalid number of bytes.');
136
+ }
137
+ const state = base64UrlEncode(stateBytes);
138
+ const scopes = this.options.scopes && this.options.scopes.length > 0
139
+ ? this.options.scopes
140
+ : exports.DEFAULT_GITHUB_AUTH_SCOPES;
141
+ const authorizationParams = new URLSearchParams({
142
+ client_id: this.options.clientId,
143
+ redirect_uri: this.options.redirectUri,
144
+ scope: scopes.join(' '),
145
+ state,
146
+ allow_signup: String(this.options.allowSignup ?? true),
147
+ });
148
+ const authorization = await this.options.authorize({
149
+ authorizationUrl: `${exports.GITHUB_AUTHORIZATION_ENDPOINT}?${authorizationParams.toString()}`,
150
+ redirectUri: this.options.redirectUri,
151
+ state,
152
+ });
153
+ if (!authorization) {
154
+ return null;
155
+ }
156
+ if (authorization.state !== state) {
157
+ throw new Error('GitHub OAuth state validation failed. Please try connecting again.');
158
+ }
159
+ if (!authorization.code) {
160
+ throw new Error('GitHub OAuth did not return an authorization code.');
161
+ }
162
+ const tokenEndpoint = this.options.tokenExchangeUrl || exports.GITHUB_TOKEN_ENDPOINT;
163
+ const requestPayload = {
164
+ client_id: this.options.clientId,
165
+ code: authorization.code,
166
+ redirect_uri: this.options.redirectUri,
167
+ state,
168
+ };
169
+ if (this.options.clientSecret) {
170
+ requestPayload.client_secret = this.options.clientSecret;
171
+ }
172
+ const response = await this.fetcher(tokenEndpoint, {
173
+ method: 'POST',
174
+ headers: {
175
+ 'Content-Type': 'application/json',
176
+ Accept: 'application/json',
177
+ },
178
+ body: JSON.stringify(requestPayload),
179
+ });
180
+ const payload = (await response.json());
181
+ if (!response.ok || payload.error) {
182
+ throw new Error(payload.error_description || payload.error || `GitHub OAuth token exchange failed (${response.status}).`);
183
+ }
184
+ if (!payload.access_token) {
185
+ throw new Error('GitHub OAuth did not return an access token.');
186
+ }
187
+ const storedTokens = {
188
+ accessToken: payload.access_token,
189
+ tokenType: payload.token_type,
190
+ scope: payload.scope,
191
+ expiresAt: payload.expires_in ? Date.now() + payload.expires_in * 1000 : undefined,
192
+ };
193
+ await this.tokenStore.setTokens(storedTokens);
194
+ const user = await this.fetchUserProfile(storedTokens.accessToken);
195
+ await this.tokenStore.setUser(user);
196
+ this.notify(user);
197
+ return user;
198
+ }
199
+ async signOut() {
200
+ await this.tokenStore.removeTokens();
201
+ await this.tokenStore.setUser(null);
202
+ this.notify(null);
203
+ }
204
+ async isSignedIn() {
205
+ const tokens = await this.tokenStore.getTokens();
206
+ return validateTokens(tokens);
207
+ }
208
+ async getUser() {
209
+ if (this.currentUser) {
210
+ return this.currentUser;
211
+ }
212
+ const cached = await this.tokenStore.getUser();
213
+ if (cached) {
214
+ this.currentUser = cached;
215
+ return cached;
216
+ }
217
+ const tokens = await this.tokenStore.getTokens();
218
+ if (!validateTokens(tokens)) {
219
+ return null;
220
+ }
221
+ try {
222
+ const user = await this.fetchUserProfile(tokens.accessToken);
223
+ await this.tokenStore.setUser(user);
224
+ this.currentUser = user;
225
+ return user;
226
+ }
227
+ catch {
228
+ return null;
229
+ }
230
+ }
231
+ async getValidAccessToken() {
232
+ const tokens = await this.tokenStore.getTokens();
233
+ if (!validateTokens(tokens)) {
234
+ throw new Error('No GitHub account signed in. Please sign in first.');
235
+ }
236
+ return tokens.accessToken;
237
+ }
238
+ async fetchUserProfile(accessToken) {
239
+ const res = await this.fetcher(`${exports.GITHUB_API_ENDPOINT}/user`, {
240
+ headers: {
241
+ Authorization: `Bearer ${accessToken}`,
242
+ Accept: 'application/vnd.github.v3+json',
243
+ 'User-Agent': 'local-lite-auth',
244
+ },
245
+ });
246
+ if (!res.ok) {
247
+ throw new Error(`Failed to fetch GitHub user profile (${res.status}).`);
248
+ }
249
+ const raw = (await res.json());
250
+ let email = raw.email;
251
+ // If email is private on profile, fetch from /user/emails endpoint
252
+ if (!email) {
253
+ try {
254
+ const emailsRes = await this.fetcher(`${exports.GITHUB_API_ENDPOINT}/user/emails`, {
255
+ headers: {
256
+ Authorization: `Bearer ${accessToken}`,
257
+ Accept: 'application/vnd.github.v3+json',
258
+ 'User-Agent': 'local-lite-auth',
259
+ },
260
+ });
261
+ if (emailsRes.ok) {
262
+ const emails = (await emailsRes.json());
263
+ const primary = emails.find(e => e.primary && e.verified) || emails[0];
264
+ if (primary)
265
+ email = primary.email;
266
+ }
267
+ }
268
+ catch {
269
+ // Fallback to username generated placeholder if emails restricted
270
+ }
271
+ }
272
+ return {
273
+ id: String(raw.id),
274
+ login: raw.login,
275
+ name: raw.name || raw.login,
276
+ email: email || `${raw.login}@users.noreply.github.com`,
277
+ avatarUrl: raw.avatar_url,
278
+ bio: raw.bio,
279
+ company: raw.company,
280
+ location: raw.location,
281
+ };
282
+ }
283
+ }
284
+ exports.GitHubAuthClient = GitHubAuthClient;
@@ -1 +1,4 @@
1
1
  export * from './google.js';
2
+ export * from './microsoft.js';
3
+ export * from './github.js';
4
+ export * from './apple.js';
@@ -15,3 +15,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./google.js"), exports);
18
+ __exportStar(require("./microsoft.js"), exports);
19
+ __exportStar(require("./github.js"), exports);
20
+ __exportStar(require("./apple.js"), exports);
@@ -0,0 +1,76 @@
1
+ export declare const MICROSOFT_GRAPH_ENDPOINT = "https://graph.microsoft.com/v1.0";
2
+ export declare const DEFAULT_MICROSOFT_AUTH_SCOPES: string[];
3
+ export interface MicrosoftUser {
4
+ id: string;
5
+ email: string;
6
+ name: string;
7
+ givenName?: string;
8
+ familyName?: string;
9
+ picture?: string;
10
+ userPrincipalName?: string;
11
+ }
12
+ export type OneDriveUser = MicrosoftUser;
13
+ export interface MicrosoftAuthTokens {
14
+ accessToken: string;
15
+ refreshToken?: string;
16
+ idToken?: string;
17
+ expiresAt: number;
18
+ tokenType?: string;
19
+ scope?: string;
20
+ }
21
+ export interface MicrosoftAuthTokenStore {
22
+ getTokens(): Promise<MicrosoftAuthTokens | null>;
23
+ setTokens(tokens: MicrosoftAuthTokens): Promise<void>;
24
+ removeTokens(): Promise<void>;
25
+ getUser(): Promise<MicrosoftUser | null>;
26
+ setUser(user: MicrosoftUser | null): Promise<void>;
27
+ }
28
+ export interface MicrosoftAuthKeyValueStorage {
29
+ getItem(key: string): Promise<unknown>;
30
+ setItem(key: string, value: unknown): Promise<void>;
31
+ removeItem(key: string): Promise<void>;
32
+ }
33
+ export interface MicrosoftAuthorizationRequest {
34
+ authorizationUrl: string;
35
+ redirectUri: string;
36
+ state: string;
37
+ }
38
+ export interface MicrosoftAuthorizationResponse {
39
+ code: string;
40
+ state: string;
41
+ }
42
+ export interface MicrosoftAuthClientOptions {
43
+ clientId: string;
44
+ redirectUri: string;
45
+ tenantId?: 'common' | 'consumers' | 'organizations' | string;
46
+ scopes?: string[];
47
+ authorize: (request: MicrosoftAuthorizationRequest) => Promise<MicrosoftAuthorizationResponse | null>;
48
+ tokenStore?: MicrosoftAuthTokenStore;
49
+ storage?: MicrosoftAuthKeyValueStorage;
50
+ randomBytes?: (length: number) => Uint8Array;
51
+ fetch?: typeof fetch;
52
+ prompt?: 'select_account' | 'consent' | 'login' | 'none';
53
+ }
54
+ export declare function createInMemoryMicrosoftTokenStore(): MicrosoftAuthTokenStore;
55
+ export declare function createMicrosoftAuthTokenStore(storage: MicrosoftAuthKeyValueStorage, prefix?: string): MicrosoftAuthTokenStore;
56
+ export declare const createOneDriveAuthTokenStore: typeof createMicrosoftAuthTokenStore;
57
+ export declare class MicrosoftAuthClient {
58
+ private readonly options;
59
+ private readonly tokenStore;
60
+ private readonly fetcher;
61
+ private readonly listeners;
62
+ private readonly tenant;
63
+ private currentUser;
64
+ constructor(options: MicrosoftAuthClientOptions);
65
+ get authorizationEndpoint(): string;
66
+ get tokenEndpoint(): string;
67
+ onAuthStateChange(listener: (user: MicrosoftUser | null) => void): () => void;
68
+ private notify;
69
+ signIn(): Promise<MicrosoftUser | null>;
70
+ signOut(): Promise<void>;
71
+ isSignedIn(): Promise<boolean>;
72
+ getUser(): Promise<MicrosoftUser | null>;
73
+ getValidAccessToken(): Promise<string>;
74
+ private fetchUserProfile;
75
+ }
76
+ export declare const OneDriveAuthClient: typeof MicrosoftAuthClient;
@@ -0,0 +1,310 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OneDriveAuthClient = exports.MicrosoftAuthClient = exports.createOneDriveAuthTokenStore = exports.DEFAULT_MICROSOFT_AUTH_SCOPES = exports.MICROSOFT_GRAPH_ENDPOINT = void 0;
4
+ exports.createInMemoryMicrosoftTokenStore = createInMemoryMicrosoftTokenStore;
5
+ exports.createMicrosoftAuthTokenStore = createMicrosoftAuthTokenStore;
6
+ const sha2_js_1 = require("@noble/hashes/sha2.js");
7
+ const utils_js_1 = require("@noble/hashes/utils.js");
8
+ exports.MICROSOFT_GRAPH_ENDPOINT = 'https://graph.microsoft.com/v1.0';
9
+ exports.DEFAULT_MICROSOFT_AUTH_SCOPES = ['openid', 'profile', 'email', 'User.Read'];
10
+ function secureRandomBytes(length) {
11
+ const crypto = globalThis.crypto;
12
+ if (!crypto?.getRandomValues) {
13
+ throw new Error('Secure random number generation is unavailable.');
14
+ }
15
+ return crypto.getRandomValues(new Uint8Array(length));
16
+ }
17
+ function base64UrlEncode(bytes) {
18
+ const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
19
+ let result = '';
20
+ for (let index = 0; index < bytes.length; index += 3) {
21
+ const first = bytes[index];
22
+ const second = bytes[index + 1];
23
+ const third = bytes[index + 2];
24
+ result += alphabet[first >> 2];
25
+ result += alphabet[((first & 3) << 4) | (second === undefined ? 0 : second >> 4)];
26
+ if (second !== undefined) {
27
+ result += alphabet[((second & 15) << 2) | (third === undefined ? 0 : third >> 6)];
28
+ }
29
+ if (third !== undefined)
30
+ result += alphabet[third & 63];
31
+ }
32
+ return result;
33
+ }
34
+ function validateTokens(value) {
35
+ if (!value || typeof value !== 'object')
36
+ return false;
37
+ const tokens = value;
38
+ return (typeof tokens.accessToken === 'string' &&
39
+ typeof tokens.expiresAt === 'number' &&
40
+ (tokens.refreshToken === undefined || typeof tokens.refreshToken === 'string') &&
41
+ (tokens.idToken === undefined || typeof tokens.idToken === 'string'));
42
+ }
43
+ function validateUser(value) {
44
+ if (!value || typeof value !== 'object')
45
+ return false;
46
+ const user = value;
47
+ return (typeof user.id === 'string' &&
48
+ typeof user.email === 'string' &&
49
+ typeof user.name === 'string');
50
+ }
51
+ function createInMemoryMicrosoftTokenStore() {
52
+ let storedTokens = null;
53
+ let storedUser = null;
54
+ return {
55
+ getTokens: async () => storedTokens,
56
+ setTokens: async (tokens) => {
57
+ storedTokens = tokens;
58
+ },
59
+ removeTokens: async () => {
60
+ storedTokens = null;
61
+ },
62
+ getUser: async () => storedUser,
63
+ setUser: async (user) => {
64
+ storedUser = user;
65
+ },
66
+ };
67
+ }
68
+ function createMicrosoftAuthTokenStore(storage, prefix = 'local-lite-microsoft-auth') {
69
+ const tokensKey = `${prefix}-tokens`;
70
+ const userKey = `${prefix}-user`;
71
+ return {
72
+ getTokens: async () => {
73
+ const raw = await storage.getItem(tokensKey);
74
+ return validateTokens(raw) ? raw : null;
75
+ },
76
+ setTokens: tokens => storage.setItem(tokensKey, tokens),
77
+ removeTokens: () => storage.removeItem(tokensKey),
78
+ getUser: async () => {
79
+ const raw = await storage.getItem(userKey);
80
+ return validateUser(raw) ? raw : null;
81
+ },
82
+ setUser: async (user) => {
83
+ if (user) {
84
+ await storage.setItem(userKey, user);
85
+ }
86
+ else {
87
+ await storage.removeItem(userKey);
88
+ }
89
+ },
90
+ };
91
+ }
92
+ exports.createOneDriveAuthTokenStore = createMicrosoftAuthTokenStore;
93
+ class MicrosoftAuthClient {
94
+ options;
95
+ tokenStore;
96
+ fetcher;
97
+ listeners = new Set();
98
+ tenant;
99
+ currentUser = null;
100
+ constructor(options) {
101
+ this.options = options;
102
+ if (!options.clientId?.trim()) {
103
+ throw new Error('Microsoft OAuth client ID is required.');
104
+ }
105
+ if (!options.redirectUri?.trim()) {
106
+ throw new Error('Microsoft OAuth redirect URI is required.');
107
+ }
108
+ this.tenant = options.tenantId?.trim() || 'common';
109
+ this.fetcher = options.fetch ?? fetch;
110
+ if (options.tokenStore) {
111
+ this.tokenStore = options.tokenStore;
112
+ }
113
+ else if (options.storage) {
114
+ this.tokenStore = createMicrosoftAuthTokenStore(options.storage);
115
+ }
116
+ else {
117
+ this.tokenStore = createInMemoryMicrosoftTokenStore();
118
+ }
119
+ }
120
+ get authorizationEndpoint() {
121
+ return `https://login.microsoftonline.com/${this.tenant}/oauth2/v2.0/authorize`;
122
+ }
123
+ get tokenEndpoint() {
124
+ return `https://login.microsoftonline.com/${this.tenant}/oauth2/v2.0/token`;
125
+ }
126
+ onAuthStateChange(listener) {
127
+ this.listeners.add(listener);
128
+ return () => {
129
+ this.listeners.delete(listener);
130
+ };
131
+ }
132
+ notify(user) {
133
+ this.currentUser = user;
134
+ for (const listener of this.listeners) {
135
+ try {
136
+ listener(user);
137
+ }
138
+ catch (err) {
139
+ console.error('Error in MicrosoftAuthClient listener:', err);
140
+ }
141
+ }
142
+ }
143
+ async signIn() {
144
+ const randomBytes = this.options.randomBytes ?? secureRandomBytes;
145
+ const verifierBytes = randomBytes(32);
146
+ const stateBytes = randomBytes(32);
147
+ if (verifierBytes.length !== 32 || stateBytes.length !== 32) {
148
+ throw new Error('The randomBytes provider returned an invalid number of bytes.');
149
+ }
150
+ const codeVerifier = base64UrlEncode(verifierBytes);
151
+ const codeChallenge = base64UrlEncode((0, sha2_js_1.sha256)((0, utils_js_1.utf8ToBytes)(codeVerifier)));
152
+ const state = base64UrlEncode(stateBytes);
153
+ const scopes = this.options.scopes && this.options.scopes.length > 0
154
+ ? this.options.scopes
155
+ : exports.DEFAULT_MICROSOFT_AUTH_SCOPES;
156
+ const authorizationParams = new URLSearchParams({
157
+ client_id: this.options.clientId,
158
+ response_type: 'code',
159
+ redirect_uri: this.options.redirectUri,
160
+ response_mode: 'query',
161
+ scope: scopes.join(' '),
162
+ prompt: this.options.prompt ?? 'select_account',
163
+ code_challenge: codeChallenge,
164
+ code_challenge_method: 'S256',
165
+ state,
166
+ });
167
+ const authorization = await this.options.authorize({
168
+ authorizationUrl: `${this.authorizationEndpoint}?${authorizationParams.toString()}`,
169
+ redirectUri: this.options.redirectUri,
170
+ state,
171
+ });
172
+ if (!authorization) {
173
+ return null;
174
+ }
175
+ if (authorization.state !== state) {
176
+ throw new Error('Microsoft OAuth state validation failed. Please try connecting again.');
177
+ }
178
+ if (!authorization.code) {
179
+ throw new Error('Microsoft OAuth did not return an authorization code.');
180
+ }
181
+ const response = await this.fetcher(this.tokenEndpoint, {
182
+ method: 'POST',
183
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
184
+ body: new URLSearchParams({
185
+ client_id: this.options.clientId,
186
+ code: authorization.code,
187
+ code_verifier: codeVerifier,
188
+ grant_type: 'authorization_code',
189
+ redirect_uri: this.options.redirectUri,
190
+ scope: scopes.join(' '),
191
+ }).toString(),
192
+ });
193
+ const payload = (await response.json());
194
+ if (!response.ok) {
195
+ throw new Error(payload.error_description || payload.error || `Microsoft OAuth token request failed (${response.status}).`);
196
+ }
197
+ if (!payload.access_token) {
198
+ throw new Error('Microsoft OAuth did not return an access token.');
199
+ }
200
+ const priorTokens = await this.tokenStore.getTokens();
201
+ const storedTokens = {
202
+ accessToken: payload.access_token,
203
+ refreshToken: payload.refresh_token ?? priorTokens?.refreshToken,
204
+ idToken: payload.id_token ?? priorTokens?.idToken,
205
+ expiresAt: Date.now() + (payload.expires_in ?? 3600) * 1000,
206
+ tokenType: payload.token_type,
207
+ scope: payload.scope,
208
+ };
209
+ await this.tokenStore.setTokens(storedTokens);
210
+ const user = await this.fetchUserProfile(storedTokens.accessToken);
211
+ await this.tokenStore.setUser(user);
212
+ this.notify(user);
213
+ return user;
214
+ }
215
+ async signOut() {
216
+ await this.tokenStore.removeTokens();
217
+ await this.tokenStore.setUser(null);
218
+ this.notify(null);
219
+ }
220
+ async isSignedIn() {
221
+ const tokens = await this.tokenStore.getTokens();
222
+ return validateTokens(tokens);
223
+ }
224
+ async getUser() {
225
+ if (this.currentUser) {
226
+ return this.currentUser;
227
+ }
228
+ const cached = await this.tokenStore.getUser();
229
+ if (cached) {
230
+ this.currentUser = cached;
231
+ return cached;
232
+ }
233
+ const tokens = await this.tokenStore.getTokens();
234
+ if (!validateTokens(tokens)) {
235
+ return null;
236
+ }
237
+ try {
238
+ const accessToken = await this.getValidAccessToken();
239
+ const user = await this.fetchUserProfile(accessToken);
240
+ await this.tokenStore.setUser(user);
241
+ this.currentUser = user;
242
+ return user;
243
+ }
244
+ catch {
245
+ return null;
246
+ }
247
+ }
248
+ async getValidAccessToken() {
249
+ const tokens = await this.tokenStore.getTokens();
250
+ if (!validateTokens(tokens)) {
251
+ throw new Error('No Microsoft account signed in. Please sign in first.');
252
+ }
253
+ if (tokens.expiresAt > Date.now() + 60_000) {
254
+ return tokens.accessToken;
255
+ }
256
+ if (!tokens.refreshToken) {
257
+ throw new Error('Your Microsoft sign-in session expired. Please sign in again.');
258
+ }
259
+ const scopes = this.options.scopes && this.options.scopes.length > 0
260
+ ? this.options.scopes
261
+ : exports.DEFAULT_MICROSOFT_AUTH_SCOPES;
262
+ const response = await this.fetcher(this.tokenEndpoint, {
263
+ method: 'POST',
264
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
265
+ body: new URLSearchParams({
266
+ client_id: this.options.clientId,
267
+ grant_type: 'refresh_token',
268
+ refresh_token: tokens.refreshToken,
269
+ scope: scopes.join(' '),
270
+ }).toString(),
271
+ });
272
+ const payload = (await response.json());
273
+ if (!response.ok) {
274
+ throw new Error(payload.error_description || payload.error || `Microsoft OAuth token refresh failed (${response.status}).`);
275
+ }
276
+ if (!payload.access_token) {
277
+ throw new Error('Microsoft OAuth token refresh did not return an access token.');
278
+ }
279
+ const updatedTokens = {
280
+ accessToken: payload.access_token,
281
+ refreshToken: payload.refresh_token ?? tokens.refreshToken,
282
+ idToken: payload.id_token ?? tokens.idToken,
283
+ expiresAt: Date.now() + (payload.expires_in ?? 3600) * 1000,
284
+ tokenType: payload.token_type ?? tokens.tokenType,
285
+ scope: payload.scope ?? tokens.scope,
286
+ };
287
+ await this.tokenStore.setTokens(updatedTokens);
288
+ return updatedTokens.accessToken;
289
+ }
290
+ async fetchUserProfile(accessToken) {
291
+ const res = await this.fetcher(`${exports.MICROSOFT_GRAPH_ENDPOINT}/me`, {
292
+ headers: { Authorization: `Bearer ${accessToken}` },
293
+ });
294
+ if (!res.ok) {
295
+ throw new Error(`Failed to fetch Microsoft user profile (${res.status}).`);
296
+ }
297
+ const raw = (await res.json());
298
+ const email = raw.mail || raw.userPrincipalName || `${raw.id}@microsoft.account`;
299
+ return {
300
+ id: raw.id,
301
+ email,
302
+ name: raw.displayName || raw.givenName || email,
303
+ givenName: raw.givenName,
304
+ familyName: raw.surname,
305
+ userPrincipalName: raw.userPrincipalName,
306
+ };
307
+ }
308
+ }
309
+ exports.MicrosoftAuthClient = MicrosoftAuthClient;
310
+ exports.OneDriveAuthClient = MicrosoftAuthClient;