@sneat/auth-core 0.1.3 → 0.1.4

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.
Files changed (58) hide show
  1. package/esm2022/index.js +8 -0
  2. package/esm2022/index.js.map +1 -0
  3. package/esm2022/lib/login-required-service.service.js +20 -0
  4. package/esm2022/lib/login-required-service.service.js.map +1 -0
  5. package/esm2022/lib/private-token-store.service.js +36 -0
  6. package/esm2022/lib/private-token-store.service.js.map +1 -0
  7. package/esm2022/lib/sneat-auth-guard.js +79 -0
  8. package/esm2022/lib/sneat-auth-guard.js.map +1 -0
  9. package/esm2022/lib/sneat-auth-state-service.js +267 -0
  10. package/esm2022/lib/sneat-auth-state-service.js.map +1 -0
  11. package/{src/lib/sneat-auth.interface.ts → esm2022/lib/sneat-auth.interface.js} +1 -5
  12. package/esm2022/lib/sneat-auth.interface.js.map +1 -0
  13. package/esm2022/lib/telegram-auth.service.js +39 -0
  14. package/esm2022/lib/telegram-auth.service.js.map +1 -0
  15. package/esm2022/lib/user/index.js +3 -0
  16. package/esm2022/lib/user/index.js.map +1 -0
  17. package/esm2022/lib/user/sneat-user.service.js +171 -0
  18. package/esm2022/lib/user/sneat-user.service.js.map +1 -0
  19. package/esm2022/lib/user/user-record.service.js +30 -0
  20. package/esm2022/lib/user/user-record.service.js.map +1 -0
  21. package/esm2022/sneat-auth-core.js +5 -0
  22. package/esm2022/sneat-auth-core.js.map +1 -0
  23. package/lib/login-required-service.service.d.ts +6 -0
  24. package/lib/private-token-store.service.d.ts +8 -0
  25. package/lib/sneat-auth-guard.d.ts +26 -0
  26. package/lib/sneat-auth-state-service.d.ts +51 -0
  27. package/lib/sneat-auth.interface.d.ts +5 -0
  28. package/lib/telegram-auth.service.d.ts +9 -0
  29. package/lib/user/sneat-user.service.d.ts +34 -0
  30. package/lib/user/user-record.service.d.ts +25 -0
  31. package/package.json +14 -2
  32. package/sneat-auth-core.d.ts +5 -0
  33. package/eslint.config.js +0 -7
  34. package/ng-package.json +0 -7
  35. package/project.json +0 -38
  36. package/src/lib/login-required-service.service.spec.ts +0 -39
  37. package/src/lib/login-required-service.service.ts +0 -14
  38. package/src/lib/private-token-store.service.spec.ts +0 -75
  39. package/src/lib/private-token-store.service.ts +0 -36
  40. package/src/lib/sneat-auth-guard.spec.ts +0 -124
  41. package/src/lib/sneat-auth-guard.ts +0 -107
  42. package/src/lib/sneat-auth-state-service.spec.ts +0 -332
  43. package/src/lib/sneat-auth-state-service.ts +0 -387
  44. package/src/lib/sneat-auth.interface.spec.ts +0 -39
  45. package/src/lib/telegram-auth.service.spec.ts +0 -186
  46. package/src/lib/telegram-auth.service.ts +0 -49
  47. package/src/lib/user/sneat-user.service.spec.ts +0 -151
  48. package/src/lib/user/sneat-user.service.ts +0 -266
  49. package/src/lib/user/user-record.service.spec.ts +0 -145
  50. package/src/lib/user/user-record.service.ts +0 -38
  51. package/src/test-setup.ts +0 -3
  52. package/tsconfig.json +0 -13
  53. package/tsconfig.lib.json +0 -19
  54. package/tsconfig.lib.prod.json +0 -7
  55. package/tsconfig.spec.json +0 -31
  56. package/vite.config.mts +0 -10
  57. /package/{src/index.ts → index.d.ts} +0 -0
  58. /package/{src/lib/user/index.ts → lib/user/index.d.ts} +0 -0
@@ -1,36 +0,0 @@
1
- import { Injectable } from '@angular/core';
2
- import { Observable, of, Subject } from 'rxjs';
3
-
4
- const tokenKey = (domain: string, projectId: string) =>
5
- `private/tokens/${domain}/${projectId}`;
6
-
7
- export const canceledByUser = 'canceled by user';
8
-
9
- @Injectable({
10
- providedIn: 'root',
11
- })
12
- export class PrivateTokenStoreService {
13
- public getPrivateToken(
14
- domain: string,
15
- projectId: string,
16
- ): Observable<string> {
17
- // Consider storing all tokens in a single item
18
- const key = tokenKey(domain, projectId);
19
- let token = localStorage.getItem(key);
20
- if (token) {
21
- return of(token);
22
- }
23
-
24
- const subj = new Subject<string>();
25
- setTimeout(() => {
26
- token = prompt(`Please provide access token for ${domain}:`);
27
- if (token) {
28
- localStorage.setItem(key, token);
29
- subj.next(token);
30
- } else {
31
- subj.error(canceledByUser);
32
- }
33
- }, 1);
34
- return subj.asObservable();
35
- }
36
- }
@@ -1,124 +0,0 @@
1
- import { TestBed } from '@angular/core/testing';
2
- import {
3
- Router,
4
- Route,
5
- UrlSegment,
6
- ActivatedRouteSnapshot,
7
- RouterStateSnapshot,
8
- } from '@angular/router';
9
- import { Auth } from '@angular/fire/auth';
10
- import {
11
- SneatAuthGuard,
12
- redirectToLoginIfNotSignedIn,
13
- } from './sneat-auth-guard';
14
- import { describe, it, expect, beforeEach, vi } from 'vitest';
15
- import { of, firstValueFrom } from 'rxjs';
16
-
17
- describe('SneatAuthGuard', () => {
18
- let guard: SneatAuthGuard;
19
- let routerMock: {
20
- createUrlTree: ReturnType<typeof vi.fn>;
21
- parseUrl: ReturnType<typeof vi.fn>;
22
- };
23
-
24
- beforeEach(() => {
25
- routerMock = {
26
- createUrlTree: vi.fn(),
27
- parseUrl: vi.fn(),
28
- };
29
-
30
- TestBed.configureTestingModule({
31
- providers: [
32
- SneatAuthGuard,
33
- { provide: Router, useValue: routerMock },
34
- {
35
- provide: Auth,
36
- useValue: {},
37
- },
38
- ],
39
- });
40
-
41
- guard = TestBed.inject(SneatAuthGuard);
42
- });
43
-
44
- it('should be created', () => {
45
- expect(guard).toBeTruthy();
46
- });
47
-
48
- describe('canLoad', () => {
49
- it('should return true', () => {
50
- const route: Route = { path: 'test' };
51
- const segments: UrlSegment[] = [];
52
-
53
- const result = guard.canLoad(route, segments);
54
- expect(result).toBe(true);
55
- });
56
- });
57
-
58
- describe('canActivate', () => {
59
- it('should return true', () => {
60
- const route = {} as ActivatedRouteSnapshot;
61
- const state = { url: '/test' } as RouterStateSnapshot;
62
-
63
- const result = guard.canActivate(route, state);
64
- expect(result).toBe(true);
65
- });
66
- });
67
-
68
- describe('canActivateChild', () => {
69
- it('should return true', () => {
70
- const childRoute = {} as ActivatedRouteSnapshot;
71
- const state = { url: '/test/child' } as RouterStateSnapshot;
72
-
73
- const result = guard.canActivateChild(childRoute, state);
74
- expect(result).toBe(true);
75
- });
76
- });
77
- });
78
-
79
- describe('redirectToLoginIfNotSignedIn', () => {
80
- it('should return true for authenticated user', async () => {
81
- const user = { uid: 'test-uid' };
82
-
83
- const result = await firstValueFrom(
84
- of(user).pipe(redirectToLoginIfNotSignedIn),
85
- );
86
- expect(result).toBe(true);
87
- });
88
-
89
- it('should return login URL for unauthenticated user at root', async () => {
90
- const originalPathname = location.pathname;
91
- Object.defineProperty(window.location, 'pathname', {
92
- writable: true,
93
- value: '/',
94
- });
95
-
96
- const result = await firstValueFrom(
97
- of(null).pipe(redirectToLoginIfNotSignedIn),
98
- );
99
- expect(result).toBe('/login');
100
-
101
- Object.defineProperty(window.location, 'pathname', {
102
- writable: true,
103
- value: originalPathname,
104
- });
105
- });
106
-
107
- it('should return login URL with hash for unauthenticated user at non-root path', async () => {
108
- const originalPathname = location.pathname;
109
- Object.defineProperty(window.location, 'pathname', {
110
- writable: true,
111
- value: '/protected',
112
- });
113
-
114
- const result = await firstValueFrom(
115
- of(null).pipe(redirectToLoginIfNotSignedIn),
116
- );
117
- expect(result).toBe('/login#/protected');
118
-
119
- Object.defineProperty(window.location, 'pathname', {
120
- writable: true,
121
- value: originalPathname,
122
- });
123
- });
124
- });
@@ -1,107 +0,0 @@
1
- import {
2
- ActivatedRouteSnapshot,
3
- // CanActivate,
4
- // CanActivateChild,
5
- // CanLoad,
6
- Route,
7
- Router,
8
- RouterStateSnapshot,
9
- UrlSegment,
10
- UrlTree,
11
- } from '@angular/router';
12
- import { Observable } from 'rxjs';
13
- import { Injectable, inject } from '@angular/core';
14
- import { Auth } from '@angular/fire/auth';
15
- import { map } from 'rxjs/operators';
16
- import { AuthPipe } from '@angular/fire/auth-guard';
17
-
18
- type AuthCanLoadPipeGenerator = (
19
- route: Route,
20
- segments: UrlSegment[],
21
- ) => AuthPipe;
22
-
23
- export const redirectToLoginIfNotSignedIn: AuthPipe = map((user) => {
24
- if (user) {
25
- return true;
26
- }
27
- let url = '/login';
28
- if (location.pathname != '/') {
29
- url += '#' + location.pathname;
30
- }
31
- return url;
32
- });
33
-
34
- @Injectable({
35
- providedIn: 'root',
36
- })
37
- export class SneatAuthGuard /*implements CanLoad, CanActivate, CanActivateChild*/ {
38
- private readonly router = inject(Router);
39
- private readonly auth = inject(Auth);
40
-
41
- public canLoad(
42
- _route: Route,
43
- _segments: UrlSegment[],
44
- ):
45
- | Observable<boolean | UrlTree>
46
- | Promise<boolean | UrlTree>
47
- | boolean
48
- | UrlTree {
49
- {
50
- // console.log('SneatAuthGuard.canLoad', route, segments);
51
- // const authPipeFactory =
52
- // (route.data && route.data['authCanLoadGuardPipe'] as AuthCanLoadPipeGenerator) ||
53
- // (() => redirectToLoginIfNotSignedIn);
54
- // const subj = new Subject<boolean>();
55
- // onAuthStateChanged(this.auth, {
56
- // next: (user) => {
57
- // console.log('onAuthStateChanged', user);
58
- // }
59
- // })
60
- // return this.auth.user.pipe(
61
- // map((user) => {
62
- // console.log('user', user);
63
- // return user;
64
- // }),
65
- // take(1),
66
- // authPipeFactory(route, segments),
67
- // map((can) => {
68
- // console.log('can', can);
69
- // if (typeof can === 'boolean') {
70
- // return can;
71
- // } else if (Array.isArray(can)) {
72
- // return this.router.createUrlTree(can);
73
- // } else {
74
- // return this.router.parseUrl(can);
75
- // }
76
- // }),
77
- // );
78
- return true;
79
- }
80
- }
81
-
82
- public canActivate(
83
- _route: ActivatedRouteSnapshot,
84
- _state: RouterStateSnapshot, //: Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree
85
- ) {
86
- // console.log('SneatAuthGuard.canActivate', route, state);
87
- return true;
88
- }
89
-
90
- canActivateChild(
91
- _childRoute: ActivatedRouteSnapshot,
92
- _state: RouterStateSnapshot, // : Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree
93
- ) {
94
- // console.log('SneatAuthGuard.canActivateChild', childRoute, state);
95
- return true;
96
- }
97
- }
98
-
99
- export const canLoad = (pipe?: AuthCanLoadPipeGenerator) => ({
100
- canLoad: [SneatAuthGuard],
101
- data: { authCanLoadGuardPipe: pipe },
102
- });
103
-
104
- export const SNEAT_AUTH_GUARDS = {
105
- canActivate: [SneatAuthGuard],
106
- canLoad: [SneatAuthGuard],
107
- };
@@ -1,332 +0,0 @@
1
- import { TestBed } from '@angular/core/testing';
2
- import { Auth, UserCredential } from '@angular/fire/auth';
3
- import { AnalyticsService, ErrorLogger } from '@sneat/core';
4
- import {
5
- SneatAuthStateService,
6
- AuthStatuses,
7
- } from './sneat-auth-state-service';
8
- import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
9
- import { firstValueFrom, Observer } from 'rxjs';
10
- import { User } from '@angular/fire/auth';
11
-
12
- // Mock Capacitor
13
- vi.mock('@capacitor/core', () => ({
14
- Capacitor: {
15
- isNativePlatform: vi.fn().mockReturnValue(false),
16
- },
17
- }));
18
-
19
- // Mock Firebase Authentication
20
- vi.mock('@capacitor-firebase/authentication', () => ({
21
- FirebaseAuthentication: {
22
- signInWithGoogle: vi.fn(),
23
- signInWithApple: vi.fn(),
24
- signInWithFacebook: vi.fn(),
25
- signInWithMicrosoft: vi.fn(),
26
- },
27
- }));
28
-
29
- // Mock firebase/auth with proper constructor mocks
30
- vi.mock('firebase/auth', () => {
31
- class MockGoogleAuthProvider {}
32
- class MockOAuthProvider {
33
- credential = vi.fn();
34
- }
35
- class MockGithubAuthProvider {
36
- addScope = vi.fn();
37
- }
38
- class MockFacebookAuthProvider {
39
- addScope = vi.fn();
40
- }
41
-
42
- return {
43
- GoogleAuthProvider: MockGoogleAuthProvider,
44
- OAuthProvider: MockOAuthProvider,
45
- GithubAuthProvider: MockGithubAuthProvider,
46
- FacebookAuthProvider: MockFacebookAuthProvider,
47
- signInWithEmailLink: vi.fn().mockResolvedValue({ user: { uid: 'test' } }),
48
- signInWithCustomToken: vi.fn().mockResolvedValue({ user: { uid: 'test' } }),
49
- signInWithPopup: vi.fn().mockResolvedValue({ user: { uid: 'test' } }),
50
- linkWithPopup: vi.fn().mockResolvedValue({ user: { uid: 'test' } }),
51
- unlink: vi.fn().mockResolvedValue({ uid: 'test', providerData: [] }),
52
- signInWithCredential: vi.fn().mockResolvedValue({ user: { uid: 'test' } }),
53
- getAuth: vi.fn().mockReturnValue({}),
54
- };
55
- });
56
-
57
- describe('SneatAuthStateService', () => {
58
- let service: SneatAuthStateService;
59
- let authMock: {
60
- onIdTokenChanged: Mock;
61
- onAuthStateChanged: Mock;
62
- signOut: Mock;
63
- currentUser: User | null;
64
- };
65
- let onAuthStateChangedCallback: Observer<User | null>;
66
- let onIdTokenChangedCallback: Observer<User | null>;
67
-
68
- beforeEach(() => {
69
- authMock = {
70
- onIdTokenChanged: vi.fn().mockImplementation((obs) => {
71
- onIdTokenChangedCallback = obs;
72
- }),
73
- onAuthStateChanged: vi.fn().mockImplementation((obs) => {
74
- onAuthStateChangedCallback = obs;
75
- }),
76
- signOut: vi.fn().mockResolvedValue(undefined),
77
- currentUser: null,
78
- };
79
-
80
- TestBed.configureTestingModule({
81
- providers: [
82
- SneatAuthStateService,
83
- {
84
- provide: ErrorLogger,
85
- useValue: { logError: vi.fn(), logErrorHandler: () => vi.fn() },
86
- },
87
- {
88
- provide: AnalyticsService,
89
- useValue: {
90
- identify: vi.fn(),
91
- logEvent: vi.fn(),
92
- },
93
- },
94
- {
95
- provide: Auth,
96
- useValue: authMock,
97
- },
98
- ],
99
- });
100
- service = TestBed.inject(SneatAuthStateService);
101
- });
102
-
103
- it('should be created', () => {
104
- expect(service).toBeTruthy();
105
- });
106
-
107
- it('should update auth status when onAuthStateChanged triggers', async () => {
108
- const fbUser = {
109
- uid: 'u1',
110
- isAnonymous: false,
111
- emailVerified: true,
112
- email: 'test@example.com',
113
- providerId: 'password',
114
- providerData: [],
115
- };
116
-
117
- onAuthStateChangedCallback.next(fbUser as unknown as User);
118
-
119
- const status = await firstValueFrom(service.authStatus);
120
- expect(status).toBe(AuthStatuses.authenticated);
121
-
122
- const user = await firstValueFrom(service.authUser);
123
- expect(user?.uid).toBe('u1');
124
- });
125
-
126
- it('should call fbAuth.signOut when signOut is called', async () => {
127
- await service.signOut();
128
- expect(authMock.signOut).toHaveBeenCalled();
129
- });
130
-
131
- it('should handle null user in onAuthStateChanged', async () => {
132
- onAuthStateChangedCallback.next(null);
133
-
134
- const status = await firstValueFrom(service.authStatus);
135
- expect(status).toBe(AuthStatuses.notAuthenticated);
136
-
137
- const user = await firstValueFrom(service.authUser);
138
- expect(user).toBeNull();
139
- });
140
-
141
- it('should handle user with multiple providerData', async () => {
142
- const fbUser = {
143
- uid: 'u2',
144
- isAnonymous: false,
145
- emailVerified: true,
146
- email: 'multi@example.com',
147
- providerId: 'firebase',
148
- providerData: [
149
- { providerId: 'google.com', uid: 'g123' },
150
- { providerId: 'facebook.com', uid: 'f456' },
151
- ],
152
- };
153
-
154
- onAuthStateChangedCallback.next(fbUser as unknown as User);
155
-
156
- const user = await firstValueFrom(service.authUser);
157
- expect(user?.uid).toBe('u2');
158
- expect(user?.providerId).toBe('firebase');
159
- });
160
-
161
- it('should handle user with single providerData', async () => {
162
- const fbUser = {
163
- uid: 'u3',
164
- isAnonymous: false,
165
- emailVerified: true,
166
- email: 'single@example.com',
167
- providerId: 'firebase',
168
- providerData: [{ providerId: 'google.com', uid: 'g789' }],
169
- };
170
-
171
- onAuthStateChangedCallback.next(fbUser as unknown as User);
172
-
173
- const user = await firstValueFrom(service.authUser);
174
- expect(user?.providerId).toBe('google.com');
175
- });
176
-
177
- it('should handle onIdTokenChanged with authenticated user', async () => {
178
- const fbUser = {
179
- uid: 'u4',
180
- isAnonymous: false,
181
- emailVerified: true,
182
- email: 'token@example.com',
183
- providerId: 'google.com',
184
- providerData: [],
185
- getIdToken: vi.fn().mockResolvedValue('mock-token-123'),
186
- };
187
-
188
- // First set auth user
189
- onAuthStateChangedCallback.next(fbUser as unknown as User);
190
-
191
- // Then trigger token changed
192
- onIdTokenChangedCallback.next(fbUser as unknown as User);
193
-
194
- // Wait for async operations
195
- await new Promise((resolve) => setTimeout(resolve, 10));
196
-
197
- const state = await firstValueFrom(service.authState);
198
- expect(state.status).toBe(AuthStatuses.authenticated);
199
- expect(state.token).toBe('mock-token-123');
200
- });
201
-
202
- it('should handle error in getIdToken', async () => {
203
- const errorLogger = TestBed.inject(ErrorLogger);
204
- const fbUser = {
205
- uid: 'u5',
206
- isAnonymous: false,
207
- emailVerified: true,
208
- email: 'error@example.com',
209
- providerId: 'google.com',
210
- providerData: [],
211
- getIdToken: vi.fn().mockRejectedValue(new Error('Token error')),
212
- };
213
-
214
- onAuthStateChangedCallback.next(fbUser as unknown as User);
215
- onIdTokenChangedCallback.next(fbUser as unknown as User);
216
-
217
- await new Promise((resolve) => setTimeout(resolve, 10));
218
-
219
- expect(errorLogger.logError).toHaveBeenCalled();
220
- });
221
-
222
- it('should handle error in onAuthStateChanged', () => {
223
- const errorLogger = TestBed.inject(ErrorLogger);
224
- const error = new Error('Auth state error');
225
-
226
- onAuthStateChangedCallback.error(error);
227
-
228
- expect(errorLogger.logError).toHaveBeenCalledWith(
229
- error,
230
- 'failed to retrieve Firebase auth user information',
231
- );
232
- });
233
-
234
- it('should handle error in onIdTokenChanged', () => {
235
- const errorLogger = TestBed.inject(ErrorLogger);
236
- const error = new Error('Token changed error');
237
-
238
- onIdTokenChangedCallback.error(error);
239
-
240
- expect(errorLogger.logError).toHaveBeenCalledWith(
241
- error,
242
- 'failed in fbAuth.onIdTokenChanged',
243
- );
244
- });
245
-
246
- describe('signInWithToken', () => {
247
- it('should call signInWithCustomToken', async () => {
248
- const { signInWithCustomToken } = await import('firebase/auth');
249
- const mockCredential = { user: { uid: 'test' } } as UserCredential;
250
- (signInWithCustomToken as Mock).mockResolvedValue(mockCredential);
251
-
252
- const result = await service.signInWithToken('custom-token');
253
-
254
- expect(signInWithCustomToken).toHaveBeenCalledWith(
255
- authMock,
256
- 'custom-token',
257
- );
258
- expect(result).toBe(mockCredential);
259
- });
260
- });
261
-
262
- describe('signInWithEmailLink', () => {
263
- it('should return observable that calls signInWithEmailLink', async () => {
264
- const { signInWithEmailLink } = await import('firebase/auth');
265
-
266
- const result = await firstValueFrom(
267
- service.signInWithEmailLink('test@example.com'),
268
- );
269
-
270
- expect(signInWithEmailLink).toHaveBeenCalled();
271
- expect(result.user.uid).toBe('test');
272
- });
273
- });
274
-
275
- describe('linkWith', () => {
276
- it('should reject if no current user', async () => {
277
- authMock.currentUser = null;
278
-
279
- await expect(service.linkWith('google.com')).rejects.toBe(
280
- 'no current user',
281
- );
282
- });
283
-
284
- it('should link provider to current user', async () => {
285
- const { linkWithPopup } = await import('firebase/auth');
286
- const mockUser = { uid: 'current-user' } as User;
287
- authMock.currentUser = mockUser;
288
-
289
- const result = await service.linkWith('google.com');
290
-
291
- expect(linkWithPopup).toHaveBeenCalled();
292
- expect(result?.user.uid).toBe('test');
293
- });
294
- });
295
-
296
- describe('unlinkAuthProvider', () => {
297
- it('should reject if no current user', async () => {
298
- authMock.currentUser = null;
299
-
300
- await expect(service.unlinkAuthProvider('google.com')).rejects.toBe(
301
- 'No user is currently signed in.',
302
- );
303
- });
304
-
305
- it('should unlink auth provider from current user', async () => {
306
- const { unlink } = await import('firebase/auth');
307
- const mockUser = {
308
- uid: 'current-user',
309
- isAnonymous: false,
310
- emailVerified: true,
311
- providerData: [],
312
- } as unknown as User;
313
- authMock.currentUser = mockUser;
314
-
315
- await service.unlinkAuthProvider('google.com');
316
-
317
- expect(unlink).toHaveBeenCalledWith(mockUser, 'google.com');
318
- });
319
-
320
- it('should handle unlink error', async () => {
321
- const { unlink } = await import('firebase/auth');
322
- const mockUser = { uid: 'current-user' } as User;
323
- authMock.currentUser = mockUser;
324
- const error = new Error('Unlink failed');
325
- (unlink as Mock).mockRejectedValueOnce(error);
326
-
327
- await expect(service.unlinkAuthProvider('google.com')).rejects.toMatch(
328
- /Failed to unlink google.com account/,
329
- );
330
- });
331
- });
332
- });