@hazeljs/oauth 0.2.0-beta.54 → 0.2.0-beta.56
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/guards/oauth-state.guard.test.d.ts +2 -0
- package/dist/guards/oauth-state.guard.test.d.ts.map +1 -0
- package/dist/guards/oauth-state.guard.test.js +60 -0
- package/dist/oauth.module.test.d.ts +2 -0
- package/dist/oauth.module.test.d.ts.map +1 -0
- package/dist/oauth.module.test.js +40 -0
- package/dist/oauth.service.js +1 -1
- package/dist/oauth.service.test.d.ts +2 -0
- package/dist/oauth.service.test.d.ts.map +1 -0
- package/dist/oauth.service.test.js +248 -0
- package/dist/providers/facebook.provider.test.d.ts +2 -0
- package/dist/providers/facebook.provider.test.d.ts.map +1 -0
- package/dist/providers/facebook.provider.test.js +66 -0
- package/dist/providers/github.provider.test.d.ts +2 -0
- package/dist/providers/github.provider.test.d.ts.map +1 -0
- package/dist/providers/github.provider.test.js +73 -0
- package/dist/providers/google.provider.test.d.ts +2 -0
- package/dist/providers/google.provider.test.d.ts.map +1 -0
- package/dist/providers/google.provider.test.js +69 -0
- package/dist/providers/microsoft.provider.test.d.ts +2 -0
- package/dist/providers/microsoft.provider.test.d.ts.map +1 -0
- package/dist/providers/microsoft.provider.test.js +62 -0
- package/dist/providers/provider.types.test.d.ts +2 -0
- package/dist/providers/provider.types.test.d.ts.map +1 -0
- package/dist/providers/provider.types.test.js +57 -0
- package/dist/providers/twitter.provider.test.d.ts +2 -0
- package/dist/providers/twitter.provider.test.d.ts.map +1 -0
- package/dist/providers/twitter.provider.test.js +71 -0
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-state.guard.test.d.ts","sourceRoot":"","sources":["../../src/guards/oauth-state.guard.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
jest.mock('arctic', () => ({
|
|
4
|
+
generateState: jest.fn(() => 'state'),
|
|
5
|
+
generateCodeVerifier: jest.fn(() => 'verifier'),
|
|
6
|
+
Google: jest.fn(),
|
|
7
|
+
MicrosoftEntraId: jest.fn(),
|
|
8
|
+
Twitter: jest.fn(),
|
|
9
|
+
GitHub: jest.fn(),
|
|
10
|
+
Facebook: jest.fn(),
|
|
11
|
+
}));
|
|
12
|
+
const oauth_state_guard_1 = require("./oauth-state.guard");
|
|
13
|
+
const core_1 = require("@hazeljs/core");
|
|
14
|
+
describe('OAuthStateGuard', () => {
|
|
15
|
+
let guard;
|
|
16
|
+
let oauthService;
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
oauthService = {
|
|
19
|
+
validateState: jest.fn().mockReturnValue(true),
|
|
20
|
+
};
|
|
21
|
+
guard = new oauth_state_guard_1.OAuthStateGuard(oauthService);
|
|
22
|
+
});
|
|
23
|
+
const createContext = (query, body, oauth_stored_state) => ({
|
|
24
|
+
switchToHttp: () => ({
|
|
25
|
+
getRequest: () => ({
|
|
26
|
+
query,
|
|
27
|
+
body,
|
|
28
|
+
oauth_stored_state,
|
|
29
|
+
}),
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
32
|
+
it('should return true when state is valid', async () => {
|
|
33
|
+
const context = createContext({ state: 'received-state' }, undefined, 'received-state');
|
|
34
|
+
const result = await guard.canActivate(context);
|
|
35
|
+
expect(result).toBe(true);
|
|
36
|
+
expect(oauthService.validateState).toHaveBeenCalledWith('received-state', 'received-state');
|
|
37
|
+
});
|
|
38
|
+
it('should use body state when query state is missing', async () => {
|
|
39
|
+
const context = createContext(undefined, { state: 'body-state' }, 'body-state');
|
|
40
|
+
const result = await guard.canActivate(context);
|
|
41
|
+
expect(result).toBe(true);
|
|
42
|
+
expect(oauthService.validateState).toHaveBeenCalledWith('body-state', 'body-state');
|
|
43
|
+
});
|
|
44
|
+
it('should throw UnauthorizedError when received state is missing', async () => {
|
|
45
|
+
const context = createContext(undefined, undefined, 'stored-state');
|
|
46
|
+
await expect(guard.canActivate(context)).rejects.toThrow(core_1.UnauthorizedError);
|
|
47
|
+
await expect(guard.canActivate(context)).rejects.toThrow('Missing OAuth state');
|
|
48
|
+
});
|
|
49
|
+
it('should throw UnauthorizedError when stored state is missing', async () => {
|
|
50
|
+
const context = createContext({ state: 'received' }, undefined, undefined);
|
|
51
|
+
await expect(guard.canActivate(context)).rejects.toThrow(core_1.UnauthorizedError);
|
|
52
|
+
await expect(guard.canActivate(context)).rejects.toThrow('Missing OAuth state');
|
|
53
|
+
});
|
|
54
|
+
it('should throw UnauthorizedError when state validation fails', async () => {
|
|
55
|
+
oauthService.validateState.mockReturnValue(false);
|
|
56
|
+
const context = createContext({ state: 'received' }, undefined, 'stored');
|
|
57
|
+
await expect(guard.canActivate(context)).rejects.toThrow(core_1.UnauthorizedError);
|
|
58
|
+
await expect(guard.canActivate(context)).rejects.toThrow('Invalid OAuth state');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.module.test.d.ts","sourceRoot":"","sources":["../src/oauth.module.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const oauth_module_1 = require("./oauth.module");
|
|
4
|
+
const oauth_service_1 = require("./oauth.service");
|
|
5
|
+
jest.mock('arctic', () => ({
|
|
6
|
+
generateState: jest.fn(() => 'state'),
|
|
7
|
+
generateCodeVerifier: jest.fn(() => 'verifier'),
|
|
8
|
+
Google: jest.fn().mockImplementation(() => ({
|
|
9
|
+
createAuthorizationURL: () => new URL('https://accounts.google.com/auth'),
|
|
10
|
+
validateAuthorizationCode: jest.fn(),
|
|
11
|
+
})),
|
|
12
|
+
MicrosoftEntraId: jest.fn(),
|
|
13
|
+
Twitter: jest.fn(),
|
|
14
|
+
GitHub: jest.fn(),
|
|
15
|
+
Facebook: jest.fn(),
|
|
16
|
+
}));
|
|
17
|
+
describe('OAuthModule', () => {
|
|
18
|
+
const testOptions = {
|
|
19
|
+
providers: {
|
|
20
|
+
google: {
|
|
21
|
+
clientId: 'test-id',
|
|
22
|
+
clientSecret: 'test-secret',
|
|
23
|
+
redirectUri: 'https://app.com/callback',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
oauth_module_1.OAuthModule.forRoot(testOptions);
|
|
29
|
+
});
|
|
30
|
+
it('should return OAuthModule from forRoot', () => {
|
|
31
|
+
const result = oauth_module_1.OAuthModule.forRoot(testOptions);
|
|
32
|
+
expect(result).toBe(oauth_module_1.OAuthModule);
|
|
33
|
+
});
|
|
34
|
+
it('should configure OAuthService when forRoot is called', () => {
|
|
35
|
+
const service = new oauth_service_1.OAuthService();
|
|
36
|
+
expect(service).toBeInstanceOf(oauth_service_1.OAuthService);
|
|
37
|
+
const url = service.getAuthorizationUrl('google');
|
|
38
|
+
expect(url.url).toBeDefined();
|
|
39
|
+
});
|
|
40
|
+
});
|
package/dist/oauth.service.js
CHANGED
|
@@ -234,6 +234,6 @@ let OAuthService = OAuthService_1 = class OAuthService {
|
|
|
234
234
|
exports.OAuthService = OAuthService;
|
|
235
235
|
OAuthService.options = null;
|
|
236
236
|
exports.OAuthService = OAuthService = OAuthService_1 = __decorate([
|
|
237
|
-
(0, core_1.
|
|
237
|
+
(0, core_1.Service)(),
|
|
238
238
|
__metadata("design:paramtypes", [])
|
|
239
239
|
], OAuthService);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.service.test.d.ts","sourceRoot":"","sources":["../src/oauth.service.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const oauth_service_1 = require("./oauth.service");
|
|
37
|
+
const arctic = __importStar(require("arctic"));
|
|
38
|
+
jest.mock('arctic', () => ({
|
|
39
|
+
generateState: jest.fn(() => 'mock-state-123'),
|
|
40
|
+
generateCodeVerifier: jest.fn(() => 'mock-code-verifier-456'),
|
|
41
|
+
Google: jest.fn().mockImplementation(() => ({
|
|
42
|
+
createAuthorizationURL: (state, _cv, scopes) => new URL(`https://accounts.google.com/o/oauth2/v2/auth?state=${state}&scope=${scopes.join(' ')}`),
|
|
43
|
+
validateAuthorizationCode: jest.fn().mockResolvedValue({
|
|
44
|
+
accessToken: () => 'mock-access-token',
|
|
45
|
+
hasRefreshToken: () => true,
|
|
46
|
+
refreshToken: () => 'mock-refresh-token',
|
|
47
|
+
accessTokenExpiresAt: () => new Date(Date.now() + 3600 * 1000),
|
|
48
|
+
}),
|
|
49
|
+
})),
|
|
50
|
+
MicrosoftEntraId: jest.fn().mockImplementation(() => ({
|
|
51
|
+
createAuthorizationURL: (state, _cv, scopes) => new URL(`https://login.microsoftonline.com/oauth2/v2.0/authorize?state=${state}&scope=${scopes.join(' ')}`),
|
|
52
|
+
validateAuthorizationCode: jest.fn().mockResolvedValue({
|
|
53
|
+
accessToken: () => 'mock-access-token',
|
|
54
|
+
hasRefreshToken: () => true,
|
|
55
|
+
refreshToken: () => 'mock-refresh-token',
|
|
56
|
+
accessTokenExpiresAt: () => new Date(Date.now() + 3600 * 1000),
|
|
57
|
+
}),
|
|
58
|
+
})),
|
|
59
|
+
Twitter: jest.fn().mockImplementation(() => ({
|
|
60
|
+
createAuthorizationURL: (state, _cv, scopes) => new URL(`https://twitter.com/i/oauth2/authorize?state=${state}&scope=${scopes.join(' ')}`),
|
|
61
|
+
validateAuthorizationCode: jest.fn().mockResolvedValue({
|
|
62
|
+
accessToken: () => 'mock-access-token',
|
|
63
|
+
hasRefreshToken: () => true,
|
|
64
|
+
refreshToken: () => 'mock-refresh-token',
|
|
65
|
+
accessTokenExpiresAt: () => new Date(Date.now() + 3600 * 1000),
|
|
66
|
+
}),
|
|
67
|
+
})),
|
|
68
|
+
GitHub: jest.fn().mockImplementation(() => ({
|
|
69
|
+
createAuthorizationURL: (state, scopes) => new URL(`https://github.com/login/oauth/authorize?state=${state}&scope=${scopes.join(' ')}`),
|
|
70
|
+
validateAuthorizationCode: jest.fn().mockResolvedValue({
|
|
71
|
+
accessToken: () => 'mock-access-token',
|
|
72
|
+
hasRefreshToken: () => true,
|
|
73
|
+
refreshToken: () => 'mock-refresh-token',
|
|
74
|
+
accessTokenExpiresAt: () => new Date(Date.now() + 3600 * 1000),
|
|
75
|
+
}),
|
|
76
|
+
})),
|
|
77
|
+
Facebook: jest.fn().mockImplementation(() => ({
|
|
78
|
+
createAuthorizationURL: (state, scopes) => new URL(`https://facebook.com/v18.0/dialog/oauth?state=${state}&scope=${scopes.join(',')}`),
|
|
79
|
+
validateAuthorizationCode: jest.fn().mockResolvedValue({
|
|
80
|
+
accessToken: () => 'mock-access-token',
|
|
81
|
+
hasRefreshToken: () => true,
|
|
82
|
+
refreshToken: () => 'mock-refresh-token',
|
|
83
|
+
accessTokenExpiresAt: () => new Date(Date.now() + 3600 * 1000),
|
|
84
|
+
}),
|
|
85
|
+
})),
|
|
86
|
+
}));
|
|
87
|
+
const mockFetch = jest.fn();
|
|
88
|
+
global.fetch = mockFetch;
|
|
89
|
+
describe('OAuthService', () => {
|
|
90
|
+
const baseConfig = {
|
|
91
|
+
clientId: 'test-client-id',
|
|
92
|
+
clientSecret: 'test-client-secret',
|
|
93
|
+
redirectUri: 'https://app.com/callback',
|
|
94
|
+
};
|
|
95
|
+
beforeEach(() => {
|
|
96
|
+
jest.clearAllMocks();
|
|
97
|
+
oauth_service_1.OAuthService.configure({
|
|
98
|
+
providers: {
|
|
99
|
+
google: { ...baseConfig },
|
|
100
|
+
github: { ...baseConfig },
|
|
101
|
+
facebook: { ...baseConfig },
|
|
102
|
+
microsoft: { ...baseConfig },
|
|
103
|
+
twitter: { ...baseConfig },
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
mockFetch.mockResolvedValue({
|
|
107
|
+
ok: true,
|
|
108
|
+
json: () => Promise.resolve({
|
|
109
|
+
sub: 'user-123',
|
|
110
|
+
email: 'test@example.com',
|
|
111
|
+
name: 'Test User',
|
|
112
|
+
picture: 'https://example.com/avatar.png',
|
|
113
|
+
}),
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
describe('configure and getOptions', () => {
|
|
117
|
+
it('should throw when not configured', () => {
|
|
118
|
+
oauth_service_1.OAuthService.options = null;
|
|
119
|
+
expect(() => new oauth_service_1.OAuthService()).toThrow('OAuthModule not configured');
|
|
120
|
+
oauth_service_1.OAuthService.configure({ providers: { google: baseConfig } });
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
describe('getAuthorizationUrl', () => {
|
|
124
|
+
it('should return URL with state for GitHub (no PKCE)', () => {
|
|
125
|
+
const service = new oauth_service_1.OAuthService();
|
|
126
|
+
const result = service.getAuthorizationUrl('github');
|
|
127
|
+
expect(result.url).toContain('github.com');
|
|
128
|
+
expect(result.state).toBe('mock-state-123');
|
|
129
|
+
expect(result.codeVerifier).toBeUndefined();
|
|
130
|
+
expect(arctic.generateState).toHaveBeenCalled();
|
|
131
|
+
});
|
|
132
|
+
it('should return URL with state and codeVerifier for Google (PKCE)', () => {
|
|
133
|
+
const service = new oauth_service_1.OAuthService();
|
|
134
|
+
const result = service.getAuthorizationUrl('google');
|
|
135
|
+
expect(result.url).toContain('accounts.google.com');
|
|
136
|
+
expect(result.state).toBe('mock-state-123');
|
|
137
|
+
expect(result.codeVerifier).toBe('mock-code-verifier-456');
|
|
138
|
+
expect(arctic.generateCodeVerifier).toHaveBeenCalled();
|
|
139
|
+
});
|
|
140
|
+
it('should use provided state when given', () => {
|
|
141
|
+
const service = new oauth_service_1.OAuthService();
|
|
142
|
+
const result = service.getAuthorizationUrl('github', 'custom-state');
|
|
143
|
+
expect(result.state).toBe('custom-state');
|
|
144
|
+
expect(result.url).toContain('custom-state');
|
|
145
|
+
});
|
|
146
|
+
it('should use custom scopes when provided', () => {
|
|
147
|
+
const service = new oauth_service_1.OAuthService();
|
|
148
|
+
const result = service.getAuthorizationUrl('github', undefined, ['user:email', 'repo']);
|
|
149
|
+
expect(result.url).toMatch(/user:email|user%3Aemail/);
|
|
150
|
+
expect(result.url).toContain('repo');
|
|
151
|
+
});
|
|
152
|
+
it('should throw for unconfigured provider', () => {
|
|
153
|
+
oauth_service_1.OAuthService.configure({ providers: { google: baseConfig } });
|
|
154
|
+
const service = new oauth_service_1.OAuthService();
|
|
155
|
+
expect(() => service.getAuthorizationUrl('github')).toThrow('GitHub OAuth is not configured');
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
describe('validateState', () => {
|
|
159
|
+
it('should return true when states match', () => {
|
|
160
|
+
const service = new oauth_service_1.OAuthService();
|
|
161
|
+
expect(service.validateState('abc123', 'abc123')).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
it('should return false when states differ', () => {
|
|
164
|
+
const service = new oauth_service_1.OAuthService();
|
|
165
|
+
expect(service.validateState('abc123', 'xyz789')).toBe(false);
|
|
166
|
+
});
|
|
167
|
+
it('should return false when received state is empty', () => {
|
|
168
|
+
const service = new oauth_service_1.OAuthService();
|
|
169
|
+
expect(service.validateState('', 'stored')).toBe(false);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
describe('generateState', () => {
|
|
173
|
+
it('should return generated state', () => {
|
|
174
|
+
const service = new oauth_service_1.OAuthService();
|
|
175
|
+
const state = service.generateState();
|
|
176
|
+
expect(state).toBe('mock-state-123');
|
|
177
|
+
expect(arctic.generateState).toHaveBeenCalled();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
describe('handleCallback', () => {
|
|
181
|
+
it('should exchange code and fetch user for GitHub', async () => {
|
|
182
|
+
const service = new oauth_service_1.OAuthService();
|
|
183
|
+
mockFetch.mockResolvedValueOnce({
|
|
184
|
+
ok: true,
|
|
185
|
+
json: () => Promise.resolve({
|
|
186
|
+
id: 12345,
|
|
187
|
+
email: 'github@example.com',
|
|
188
|
+
name: 'GitHub User',
|
|
189
|
+
avatar_url: 'https://github.com/avatar.png',
|
|
190
|
+
}),
|
|
191
|
+
});
|
|
192
|
+
const result = await service.handleCallback('github', 'auth-code', 'mock-state-123');
|
|
193
|
+
expect(result.accessToken).toBe('mock-access-token');
|
|
194
|
+
expect(result.user.id).toBe('12345');
|
|
195
|
+
expect(result.user.email).toBe('github@example.com');
|
|
196
|
+
expect(result.user.name).toBe('GitHub User');
|
|
197
|
+
expect(result.user.picture).toBe('https://github.com/avatar.png');
|
|
198
|
+
});
|
|
199
|
+
it('should require codeVerifier for Google (PKCE)', async () => {
|
|
200
|
+
const service = new oauth_service_1.OAuthService();
|
|
201
|
+
await expect(service.handleCallback('google', 'code', 'state')).rejects.toThrow('requires codeVerifier');
|
|
202
|
+
});
|
|
203
|
+
it('should exchange code with codeVerifier for Google', async () => {
|
|
204
|
+
const service = new oauth_service_1.OAuthService();
|
|
205
|
+
const result = await service.handleCallback('google', 'auth-code', 'mock-state-123', 'mock-code-verifier-456');
|
|
206
|
+
expect(result.accessToken).toBe('mock-access-token');
|
|
207
|
+
expect(result.user.email).toBe('test@example.com');
|
|
208
|
+
});
|
|
209
|
+
it('should fetch Facebook user with correct API', async () => {
|
|
210
|
+
const service = new oauth_service_1.OAuthService();
|
|
211
|
+
mockFetch.mockResolvedValue({
|
|
212
|
+
ok: true,
|
|
213
|
+
json: () => Promise.resolve({
|
|
214
|
+
id: 'fb-123',
|
|
215
|
+
email: 'fb@example.com',
|
|
216
|
+
name: 'FB User',
|
|
217
|
+
picture: { data: { url: 'https://fb.com/pic.png' } },
|
|
218
|
+
}),
|
|
219
|
+
});
|
|
220
|
+
const result = await service.handleCallback('facebook', 'code', 'state');
|
|
221
|
+
expect(result.user.id).toBe('fb-123');
|
|
222
|
+
expect(result.user.picture).toBe('https://fb.com/pic.png');
|
|
223
|
+
});
|
|
224
|
+
it('should fetch Twitter user (no email from API v2)', async () => {
|
|
225
|
+
const service = new oauth_service_1.OAuthService();
|
|
226
|
+
mockFetch.mockResolvedValue({
|
|
227
|
+
ok: true,
|
|
228
|
+
json: () => Promise.resolve({
|
|
229
|
+
data: {
|
|
230
|
+
id: 'tw-123',
|
|
231
|
+
name: 'Twitter User',
|
|
232
|
+
username: 'twuser',
|
|
233
|
+
profile_image_url: 'https://twitter.com/pic.png',
|
|
234
|
+
},
|
|
235
|
+
}),
|
|
236
|
+
});
|
|
237
|
+
const result = await service.handleCallback('twitter', 'code', 'state', 'code-verifier');
|
|
238
|
+
expect(result.user.id).toBe('tw-123');
|
|
239
|
+
expect(result.user.email).toBe('');
|
|
240
|
+
expect(result.user.name).toBe('Twitter User');
|
|
241
|
+
});
|
|
242
|
+
it('should throw for unconfigured provider', async () => {
|
|
243
|
+
oauth_service_1.OAuthService.configure({ providers: { google: baseConfig } });
|
|
244
|
+
const service = new oauth_service_1.OAuthService();
|
|
245
|
+
await expect(service.handleCallback('facebook', 'code', 'state')).rejects.toThrow('Facebook OAuth is not configured');
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"facebook.provider.test.d.ts","sourceRoot":"","sources":["../../src/providers/facebook.provider.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const facebook_provider_1 = require("./facebook.provider");
|
|
4
|
+
jest.mock('arctic', () => ({
|
|
5
|
+
Facebook: jest.fn().mockImplementation(() => ({})),
|
|
6
|
+
}));
|
|
7
|
+
describe('Facebook Provider', () => {
|
|
8
|
+
const config = {
|
|
9
|
+
clientId: 'test-id',
|
|
10
|
+
clientSecret: 'test-secret',
|
|
11
|
+
redirectUri: 'https://app.com/callback',
|
|
12
|
+
};
|
|
13
|
+
describe('createFacebookProvider', () => {
|
|
14
|
+
it('should create provider instance', () => {
|
|
15
|
+
const provider = (0, facebook_provider_1.createFacebookProvider)(config);
|
|
16
|
+
expect(provider).toBeDefined();
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
describe('getFacebookDefaultScopes', () => {
|
|
20
|
+
it('should return default scopes', () => {
|
|
21
|
+
const scopes = (0, facebook_provider_1.getFacebookDefaultScopes)();
|
|
22
|
+
expect(scopes).toContain('email');
|
|
23
|
+
expect(scopes).toContain('public_profile');
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
describe('fetchFacebookUser', () => {
|
|
27
|
+
const originalFetch = global.fetch;
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
global.fetch = jest.fn();
|
|
30
|
+
});
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
global.fetch = originalFetch;
|
|
33
|
+
});
|
|
34
|
+
it('should fetch and map user data with nested picture', async () => {
|
|
35
|
+
global.fetch.mockResolvedValue({
|
|
36
|
+
ok: true,
|
|
37
|
+
json: () => Promise.resolve({
|
|
38
|
+
id: 'fb-123',
|
|
39
|
+
email: 'user@fb.com',
|
|
40
|
+
name: 'FB User',
|
|
41
|
+
picture: { data: { url: 'https://fb.com/pic.jpg' } },
|
|
42
|
+
}),
|
|
43
|
+
});
|
|
44
|
+
const user = await (0, facebook_provider_1.fetchFacebookUser)('access-token');
|
|
45
|
+
expect(user.id).toBe('fb-123');
|
|
46
|
+
expect(user.email).toBe('user@fb.com');
|
|
47
|
+
expect(user.name).toBe('FB User');
|
|
48
|
+
expect(user.picture).toBe('https://fb.com/pic.jpg');
|
|
49
|
+
});
|
|
50
|
+
it('should handle missing picture', async () => {
|
|
51
|
+
global.fetch.mockResolvedValue({
|
|
52
|
+
ok: true,
|
|
53
|
+
json: () => Promise.resolve({
|
|
54
|
+
id: 'fb-456',
|
|
55
|
+
name: 'No Pic User',
|
|
56
|
+
}),
|
|
57
|
+
});
|
|
58
|
+
const user = await (0, facebook_provider_1.fetchFacebookUser)('access-token');
|
|
59
|
+
expect(user.picture).toBeNull();
|
|
60
|
+
});
|
|
61
|
+
it('should throw on failed request', async () => {
|
|
62
|
+
global.fetch.mockResolvedValue({ ok: false });
|
|
63
|
+
await expect((0, facebook_provider_1.fetchFacebookUser)('bad-token')).rejects.toThrow('Failed to fetch Facebook user');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.provider.test.d.ts","sourceRoot":"","sources":["../../src/providers/github.provider.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const github_provider_1 = require("./github.provider");
|
|
4
|
+
jest.mock('arctic', () => ({
|
|
5
|
+
GitHub: jest.fn().mockImplementation(() => ({})),
|
|
6
|
+
}));
|
|
7
|
+
describe('GitHub Provider', () => {
|
|
8
|
+
const config = {
|
|
9
|
+
clientId: 'test-id',
|
|
10
|
+
clientSecret: 'test-secret',
|
|
11
|
+
redirectUri: 'https://app.com/callback',
|
|
12
|
+
};
|
|
13
|
+
describe('createGitHubProvider', () => {
|
|
14
|
+
it('should create provider instance', () => {
|
|
15
|
+
const provider = (0, github_provider_1.createGitHubProvider)(config);
|
|
16
|
+
expect(provider).toBeDefined();
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
describe('getGitHubDefaultScopes', () => {
|
|
20
|
+
it('should return default scopes', () => {
|
|
21
|
+
const scopes = (0, github_provider_1.getGitHubDefaultScopes)();
|
|
22
|
+
expect(scopes).toContain('user:email');
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
describe('fetchGitHubUser', () => {
|
|
26
|
+
const originalFetch = global.fetch;
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
global.fetch = jest.fn();
|
|
29
|
+
});
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
global.fetch = originalFetch;
|
|
32
|
+
});
|
|
33
|
+
it('should fetch and map user data', async () => {
|
|
34
|
+
global.fetch.mockResolvedValueOnce({
|
|
35
|
+
ok: true,
|
|
36
|
+
json: () => Promise.resolve({
|
|
37
|
+
id: 999,
|
|
38
|
+
email: 'dev@github.com',
|
|
39
|
+
name: 'Dev User',
|
|
40
|
+
avatar_url: 'https://github.com/avatar.png',
|
|
41
|
+
}),
|
|
42
|
+
});
|
|
43
|
+
const user = await (0, github_provider_1.fetchGitHubUser)('access-token');
|
|
44
|
+
expect(user.id).toBe('999');
|
|
45
|
+
expect(user.email).toBe('dev@github.com');
|
|
46
|
+
expect(user.name).toBe('Dev User');
|
|
47
|
+
expect(user.picture).toBe('https://github.com/avatar.png');
|
|
48
|
+
});
|
|
49
|
+
it('should fetch email from emails endpoint when not in user', async () => {
|
|
50
|
+
global.fetch
|
|
51
|
+
.mockResolvedValueOnce({
|
|
52
|
+
ok: true,
|
|
53
|
+
json: () => Promise.resolve({
|
|
54
|
+
id: 888,
|
|
55
|
+
name: 'No Email User',
|
|
56
|
+
}),
|
|
57
|
+
})
|
|
58
|
+
.mockResolvedValueOnce({
|
|
59
|
+
ok: true,
|
|
60
|
+
json: () => Promise.resolve([
|
|
61
|
+
{ email: 'primary@example.com', primary: true },
|
|
62
|
+
{ email: 'other@example.com', primary: false },
|
|
63
|
+
]),
|
|
64
|
+
});
|
|
65
|
+
const user = await (0, github_provider_1.fetchGitHubUser)('access-token');
|
|
66
|
+
expect(user.email).toBe('primary@example.com');
|
|
67
|
+
});
|
|
68
|
+
it('should throw on failed request', async () => {
|
|
69
|
+
global.fetch.mockResolvedValue({ ok: false });
|
|
70
|
+
await expect((0, github_provider_1.fetchGitHubUser)('bad-token')).rejects.toThrow('Failed to fetch GitHub user');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google.provider.test.d.ts","sourceRoot":"","sources":["../../src/providers/google.provider.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const google_provider_1 = require("./google.provider");
|
|
4
|
+
jest.mock('arctic', () => ({
|
|
5
|
+
Google: jest.fn().mockImplementation(() => ({})),
|
|
6
|
+
}));
|
|
7
|
+
describe('Google Provider', () => {
|
|
8
|
+
const config = {
|
|
9
|
+
clientId: 'test-id',
|
|
10
|
+
clientSecret: 'test-secret',
|
|
11
|
+
redirectUri: 'https://app.com/callback',
|
|
12
|
+
};
|
|
13
|
+
describe('createGoogleProvider', () => {
|
|
14
|
+
it('should create provider instance', () => {
|
|
15
|
+
const provider = (0, google_provider_1.createGoogleProvider)(config);
|
|
16
|
+
expect(provider).toBeDefined();
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
describe('getGoogleDefaultScopes', () => {
|
|
20
|
+
it('should return default scopes', () => {
|
|
21
|
+
const scopes = (0, google_provider_1.getGoogleDefaultScopes)();
|
|
22
|
+
expect(scopes).toContain('openid');
|
|
23
|
+
expect(scopes).toContain('profile');
|
|
24
|
+
expect(scopes).toContain('email');
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
describe('fetchGoogleUser', () => {
|
|
28
|
+
const originalFetch = global.fetch;
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
global.fetch = jest.fn();
|
|
31
|
+
});
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
global.fetch = originalFetch;
|
|
34
|
+
});
|
|
35
|
+
it('should fetch and map user data', async () => {
|
|
36
|
+
global.fetch.mockResolvedValue({
|
|
37
|
+
ok: true,
|
|
38
|
+
json: () => Promise.resolve({
|
|
39
|
+
sub: 'google-123',
|
|
40
|
+
email: 'user@gmail.com',
|
|
41
|
+
name: 'Google User',
|
|
42
|
+
picture: 'https://google.com/photo.jpg',
|
|
43
|
+
}),
|
|
44
|
+
});
|
|
45
|
+
const user = await (0, google_provider_1.fetchGoogleUser)('access-token');
|
|
46
|
+
expect(user.id).toBe('google-123');
|
|
47
|
+
expect(user.email).toBe('user@gmail.com');
|
|
48
|
+
expect(user.name).toBe('Google User');
|
|
49
|
+
expect(user.picture).toBe('https://google.com/photo.jpg');
|
|
50
|
+
});
|
|
51
|
+
it('should handle missing optional fields', async () => {
|
|
52
|
+
global.fetch.mockResolvedValue({
|
|
53
|
+
ok: true,
|
|
54
|
+
json: () => Promise.resolve({
|
|
55
|
+
sub: 'google-456',
|
|
56
|
+
}),
|
|
57
|
+
});
|
|
58
|
+
const user = await (0, google_provider_1.fetchGoogleUser)('access-token');
|
|
59
|
+
expect(user.id).toBe('google-456');
|
|
60
|
+
expect(user.email).toBe('');
|
|
61
|
+
expect(user.name).toBeNull();
|
|
62
|
+
expect(user.picture).toBeNull();
|
|
63
|
+
});
|
|
64
|
+
it('should throw on failed request', async () => {
|
|
65
|
+
global.fetch.mockResolvedValue({ ok: false });
|
|
66
|
+
await expect((0, google_provider_1.fetchGoogleUser)('bad-token')).rejects.toThrow('Failed to fetch Google user');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"microsoft.provider.test.d.ts","sourceRoot":"","sources":["../../src/providers/microsoft.provider.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const microsoft_provider_1 = require("./microsoft.provider");
|
|
4
|
+
jest.mock('arctic', () => ({
|
|
5
|
+
MicrosoftEntraId: jest.fn().mockImplementation(() => ({})),
|
|
6
|
+
}));
|
|
7
|
+
describe('Microsoft Provider', () => {
|
|
8
|
+
const config = {
|
|
9
|
+
clientId: 'test-id',
|
|
10
|
+
clientSecret: 'test-secret',
|
|
11
|
+
redirectUri: 'https://app.com/callback',
|
|
12
|
+
};
|
|
13
|
+
describe('createMicrosoftProvider', () => {
|
|
14
|
+
it('should create provider with common tenant by default', () => {
|
|
15
|
+
const provider = (0, microsoft_provider_1.createMicrosoftProvider)(config);
|
|
16
|
+
expect(provider).toBeDefined();
|
|
17
|
+
});
|
|
18
|
+
it('should create provider with custom tenant', () => {
|
|
19
|
+
const provider = (0, microsoft_provider_1.createMicrosoftProvider)({
|
|
20
|
+
...config,
|
|
21
|
+
tenant: 'my-tenant-id',
|
|
22
|
+
});
|
|
23
|
+
expect(provider).toBeDefined();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
describe('getMicrosoftDefaultScopes', () => {
|
|
27
|
+
it('should return default scopes', () => {
|
|
28
|
+
const scopes = (0, microsoft_provider_1.getMicrosoftDefaultScopes)();
|
|
29
|
+
expect(scopes).toContain('openid');
|
|
30
|
+
expect(scopes).toContain('profile');
|
|
31
|
+
expect(scopes).toContain('email');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe('fetchMicrosoftUser', () => {
|
|
35
|
+
const originalFetch = global.fetch;
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
global.fetch = jest.fn();
|
|
38
|
+
});
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
global.fetch = originalFetch;
|
|
41
|
+
});
|
|
42
|
+
it('should fetch and map user data', async () => {
|
|
43
|
+
global.fetch.mockResolvedValue({
|
|
44
|
+
ok: true,
|
|
45
|
+
json: () => Promise.resolve({
|
|
46
|
+
sub: 'ms-123',
|
|
47
|
+
email: 'user@outlook.com',
|
|
48
|
+
name: 'MS User',
|
|
49
|
+
picture: 'https://graph.microsoft.com/photo',
|
|
50
|
+
}),
|
|
51
|
+
});
|
|
52
|
+
const user = await (0, microsoft_provider_1.fetchMicrosoftUser)('access-token');
|
|
53
|
+
expect(user.id).toBe('ms-123');
|
|
54
|
+
expect(user.email).toBe('user@outlook.com');
|
|
55
|
+
expect(user.name).toBe('MS User');
|
|
56
|
+
});
|
|
57
|
+
it('should throw on failed request', async () => {
|
|
58
|
+
global.fetch.mockResolvedValue({ ok: false });
|
|
59
|
+
await expect((0, microsoft_provider_1.fetchMicrosoftUser)('bad-token')).rejects.toThrow('Failed to fetch Microsoft user');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.types.test.d.ts","sourceRoot":"","sources":["../../src/providers/provider.types.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
describe('Provider Types', () => {
|
|
4
|
+
it('should allow valid BaseProviderConfig', () => {
|
|
5
|
+
const config = {
|
|
6
|
+
clientId: 'id',
|
|
7
|
+
clientSecret: 'secret',
|
|
8
|
+
redirectUri: 'https://app.com/cb',
|
|
9
|
+
};
|
|
10
|
+
expect(config.clientId).toBe('id');
|
|
11
|
+
});
|
|
12
|
+
it('should allow GoogleProviderConfig', () => {
|
|
13
|
+
const config = {
|
|
14
|
+
clientId: 'id',
|
|
15
|
+
clientSecret: 'secret',
|
|
16
|
+
redirectUri: 'https://app.com/cb',
|
|
17
|
+
};
|
|
18
|
+
expect(config).toBeDefined();
|
|
19
|
+
});
|
|
20
|
+
it('should allow MicrosoftProviderConfig with optional tenant', () => {
|
|
21
|
+
const config = {
|
|
22
|
+
clientId: 'id',
|
|
23
|
+
clientSecret: 'secret',
|
|
24
|
+
redirectUri: 'https://app.com/cb',
|
|
25
|
+
tenant: 'common',
|
|
26
|
+
};
|
|
27
|
+
expect(config.tenant).toBe('common');
|
|
28
|
+
});
|
|
29
|
+
it('should define OAuthUser shape', () => {
|
|
30
|
+
const user = {
|
|
31
|
+
id: '123',
|
|
32
|
+
email: 'a@b.com',
|
|
33
|
+
name: 'User',
|
|
34
|
+
picture: 'https://pic.com',
|
|
35
|
+
};
|
|
36
|
+
expect(user.id).toBe('123');
|
|
37
|
+
});
|
|
38
|
+
it('should define OAuthCallbackResult shape', () => {
|
|
39
|
+
const result = {
|
|
40
|
+
accessToken: 'token',
|
|
41
|
+
user: { id: '1', email: 'e@e.com', name: 'N' },
|
|
42
|
+
};
|
|
43
|
+
expect(result.accessToken).toBe('token');
|
|
44
|
+
});
|
|
45
|
+
it('should define OAuthAuthorizationResult shape', () => {
|
|
46
|
+
const result = {
|
|
47
|
+
url: 'https://provider.com',
|
|
48
|
+
state: 'state',
|
|
49
|
+
codeVerifier: 'verifier',
|
|
50
|
+
};
|
|
51
|
+
expect(result.codeVerifier).toBe('verifier');
|
|
52
|
+
});
|
|
53
|
+
it('should include all SupportedProvider values', () => {
|
|
54
|
+
const providers = ['google', 'microsoft', 'github', 'facebook', 'twitter'];
|
|
55
|
+
expect(providers).toHaveLength(5);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twitter.provider.test.d.ts","sourceRoot":"","sources":["../../src/providers/twitter.provider.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const twitter_provider_1 = require("./twitter.provider");
|
|
4
|
+
jest.mock('arctic', () => ({
|
|
5
|
+
Twitter: jest.fn().mockImplementation(() => ({})),
|
|
6
|
+
}));
|
|
7
|
+
describe('Twitter Provider', () => {
|
|
8
|
+
const config = {
|
|
9
|
+
clientId: 'test-id',
|
|
10
|
+
clientSecret: 'test-secret',
|
|
11
|
+
redirectUri: 'https://app.com/callback',
|
|
12
|
+
};
|
|
13
|
+
describe('createTwitterProvider', () => {
|
|
14
|
+
it('should create provider with client secret', () => {
|
|
15
|
+
const provider = (0, twitter_provider_1.createTwitterProvider)(config);
|
|
16
|
+
expect(provider).toBeDefined();
|
|
17
|
+
});
|
|
18
|
+
it('should create provider without client secret', () => {
|
|
19
|
+
const provider = (0, twitter_provider_1.createTwitterProvider)({
|
|
20
|
+
clientId: config.clientId,
|
|
21
|
+
redirectUri: config.redirectUri,
|
|
22
|
+
});
|
|
23
|
+
expect(provider).toBeDefined();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
describe('getTwitterDefaultScopes', () => {
|
|
27
|
+
it('should return default scopes', () => {
|
|
28
|
+
const scopes = (0, twitter_provider_1.getTwitterDefaultScopes)();
|
|
29
|
+
expect(scopes).toContain('users.read');
|
|
30
|
+
expect(scopes).toContain('tweet.read');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe('fetchTwitterUser', () => {
|
|
34
|
+
const originalFetch = global.fetch;
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
global.fetch = jest.fn();
|
|
37
|
+
});
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
global.fetch = originalFetch;
|
|
40
|
+
});
|
|
41
|
+
it('should fetch and map user data', async () => {
|
|
42
|
+
global.fetch.mockResolvedValue({
|
|
43
|
+
ok: true,
|
|
44
|
+
json: () => Promise.resolve({
|
|
45
|
+
data: {
|
|
46
|
+
id: 'tw-999',
|
|
47
|
+
name: 'Twitter User',
|
|
48
|
+
username: 'twuser',
|
|
49
|
+
profile_image_url: 'https://twitter.com/pic.png',
|
|
50
|
+
},
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
const user = await (0, twitter_provider_1.fetchTwitterUser)('access-token');
|
|
54
|
+
expect(user.id).toBe('tw-999');
|
|
55
|
+
expect(user.name).toBe('Twitter User');
|
|
56
|
+
expect(user.email).toBe('');
|
|
57
|
+
expect(user.picture).toBe('https://twitter.com/pic.png');
|
|
58
|
+
});
|
|
59
|
+
it('should throw when no user data', async () => {
|
|
60
|
+
global.fetch.mockResolvedValue({
|
|
61
|
+
ok: true,
|
|
62
|
+
json: () => Promise.resolve({}),
|
|
63
|
+
});
|
|
64
|
+
await expect((0, twitter_provider_1.fetchTwitterUser)('bad-token')).rejects.toThrow('Twitter API returned no user data');
|
|
65
|
+
});
|
|
66
|
+
it('should throw on failed request', async () => {
|
|
67
|
+
global.fetch.mockResolvedValue({ ok: false });
|
|
68
|
+
await expect((0, twitter_provider_1.fetchTwitterUser)('bad-token')).rejects.toThrow('Failed to fetch Twitter user');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hazeljs/oauth",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.56",
|
|
4
4
|
"description": "OAuth 2.0 social login module for HazelJS - Microsoft, Google, GitHub and more",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -52,5 +52,5 @@
|
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"@hazeljs/core": ">=0.2.0-beta.0"
|
|
54
54
|
},
|
|
55
|
-
"gitHead": "
|
|
55
|
+
"gitHead": "c2737e90974458a8438eee623726f0a453b66b8b"
|
|
56
56
|
}
|