@devoven/oauth 0.1.0
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 +281 -0
- package/dist/application/ports/get-auth-url.port.d.ts +3 -0
- package/dist/application/ports/get-auth-url.port.js +3 -0
- package/dist/application/ports/get-auth-url.port.js.map +1 -0
- package/dist/application/ports/handle-callback.port.d.ts +4 -0
- package/dist/application/ports/handle-callback.port.js +3 -0
- package/dist/application/ports/handle-callback.port.js.map +1 -0
- package/dist/application/ports/oauth-provider.port.d.ts +15 -0
- package/dist/application/ports/oauth-provider.port.js +3 -0
- package/dist/application/ports/oauth-provider.port.js.map +1 -0
- package/dist/application/ports/oauth-state-store.port.d.ts +4 -0
- package/dist/application/ports/oauth-state-store.port.js +3 -0
- package/dist/application/ports/oauth-state-store.port.js.map +1 -0
- package/dist/application/use-cases/get-auth-url.use-case.d.ts +9 -0
- package/dist/application/use-cases/get-auth-url.use-case.js +38 -0
- package/dist/application/use-cases/get-auth-url.use-case.js.map +1 -0
- package/dist/application/use-cases/handle-callback.use-case.d.ts +10 -0
- package/dist/application/use-cases/handle-callback.use-case.js +42 -0
- package/dist/application/use-cases/handle-callback.use-case.js.map +1 -0
- package/dist/domain/entities/oauth-profile.entity.d.ts +16 -0
- package/dist/domain/entities/oauth-profile.entity.js +26 -0
- package/dist/domain/entities/oauth-profile.entity.js.map +1 -0
- package/dist/domain/value-objects/oauth-token.vo.d.ts +15 -0
- package/dist/domain/value-objects/oauth-token.vo.js +26 -0
- package/dist/domain/value-objects/oauth-token.vo.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/infrastructure/providers/google-oauth.provider.d.ts +15 -0
- package/dist/infrastructure/providers/google-oauth.provider.js +111 -0
- package/dist/infrastructure/providers/google-oauth.provider.js.map +1 -0
- package/dist/infrastructure/providers/line-oauth.provider.d.ts +16 -0
- package/dist/infrastructure/providers/line-oauth.provider.js +118 -0
- package/dist/infrastructure/providers/line-oauth.provider.js.map +1 -0
- package/dist/infrastructure/state/in-memory-state-store.d.ts +8 -0
- package/dist/infrastructure/state/in-memory-state-store.js +49 -0
- package/dist/infrastructure/state/in-memory-state-store.js.map +1 -0
- package/dist/oauth.module-definition.d.ts +18 -0
- package/dist/oauth.module-definition.js +9 -0
- package/dist/oauth.module-definition.js.map +1 -0
- package/dist/oauth.module.d.ts +8 -0
- package/dist/oauth.module.js +102 -0
- package/dist/oauth.module.js.map +1 -0
- package/dist/presentation/controllers/oauth.controller.d.ts +17 -0
- package/dist/presentation/controllers/oauth.controller.js +73 -0
- package/dist/presentation/controllers/oauth.controller.js.map +1 -0
- package/dist/presentation/dto/oauth-callback-query.dto.d.ts +4 -0
- package/dist/presentation/dto/oauth-callback-query.dto.js +27 -0
- package/dist/presentation/dto/oauth-callback-query.dto.js.map +1 -0
- package/dist/presentation/dto/oauth-profile-response.dto.d.ts +9 -0
- package/dist/presentation/dto/oauth-profile-response.dto.js +16 -0
- package/dist/presentation/dto/oauth-profile-response.dto.js.map +1 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# @devoven/oauth
|
|
2
|
+
|
|
3
|
+
OAuth 2.0 / OIDC module for multi-provider authentication in NestJS. Wire up Google, LINE, or any custom provider with a single import.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @devoven/oauth
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @devoven/oauth
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Peer Dependencies
|
|
14
|
+
|
|
15
|
+
Install the standard NestJS validation stack if your app does not already have it:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install class-validator class-transformer
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`@nestjs/common`, `@nestjs/core`, `rxjs`, and `reflect-metadata` are expected to already be present in any NestJS application.
|
|
22
|
+
|
|
23
|
+
`google-auth-library` is bundled with this package and does not need to be installed separately.
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { OAuthModule, GoogleOAuthProvider } from '@devoven/oauth';
|
|
29
|
+
|
|
30
|
+
@Module({
|
|
31
|
+
imports: [
|
|
32
|
+
OAuthModule.register({
|
|
33
|
+
providers: [
|
|
34
|
+
{
|
|
35
|
+
name: 'google',
|
|
36
|
+
provider: GoogleOAuthProvider,
|
|
37
|
+
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
38
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
39
|
+
callbackUrl: 'https://example.com/oauth/google/callback',
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
}),
|
|
43
|
+
],
|
|
44
|
+
})
|
|
45
|
+
export class AppModule {}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
This registers the `OAuthController` at `/oauth`, giving you:
|
|
49
|
+
|
|
50
|
+
- `GET /oauth/google` — redirects the user to Google's consent screen
|
|
51
|
+
- `GET /oauth/google/callback` — handles the code exchange and returns the resolved profile
|
|
52
|
+
|
|
53
|
+
## Module Options
|
|
54
|
+
|
|
55
|
+
| Option | Type | Default | Description |
|
|
56
|
+
|--------|------|---------|-------------|
|
|
57
|
+
| `providers` | `Array<OAuthProviderOption \| OAuthProviderPort>` | — (required) | Provider configs or pre-constructed provider instances |
|
|
58
|
+
| `stateStore` | Class or instance of `OAuthStateStorePort` | `InMemoryStateStore` | CSRF state store. Default TTL is 5 minutes |
|
|
59
|
+
| `controller` | `boolean` | `true` | Mount `OAuthController`. Set to `false` to handle routes yourself |
|
|
60
|
+
| `callbackSuccessUrl` | `string` | `undefined` | When set, the callback endpoint redirects to this URL with profile fields as query parameters instead of returning JSON |
|
|
61
|
+
|
|
62
|
+
### OAuthProviderOption
|
|
63
|
+
|
|
64
|
+
| Field | Type | Description |
|
|
65
|
+
|-------|------|-------------|
|
|
66
|
+
| `name` | `string` | Key used in the URL path (e.g. `'google'` → `/oauth/google`) |
|
|
67
|
+
| `provider` | Constructor | A class implementing `OAuthProviderPort` (e.g. `GoogleOAuthProvider`) |
|
|
68
|
+
| `clientId` | `string` | OAuth app client ID |
|
|
69
|
+
| `clientSecret` | `string` | OAuth app client secret |
|
|
70
|
+
| `callbackUrl` | `string` | Absolute URL registered with the provider |
|
|
71
|
+
| `scopes` | `string[]` | Optional scope list. Defaults to the provider's built-in defaults |
|
|
72
|
+
|
|
73
|
+
Alternatively, pass a pre-constructed `OAuthProviderPort` instance directly in the `providers` array.
|
|
74
|
+
|
|
75
|
+
## Async Registration
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { OAuthModule, GoogleOAuthProvider, LineOAuthProvider } from '@devoven/oauth';
|
|
79
|
+
import { ConfigService } from '@nestjs/config';
|
|
80
|
+
|
|
81
|
+
@Module({
|
|
82
|
+
imports: [
|
|
83
|
+
OAuthModule.registerAsync({
|
|
84
|
+
useFactory: (config: ConfigService) => ({
|
|
85
|
+
callbackSuccessUrl: config.get('OAUTH_SUCCESS_URL'),
|
|
86
|
+
providers: [
|
|
87
|
+
{
|
|
88
|
+
name: 'google',
|
|
89
|
+
provider: GoogleOAuthProvider,
|
|
90
|
+
clientId: config.get('GOOGLE_CLIENT_ID'),
|
|
91
|
+
clientSecret: config.get('GOOGLE_CLIENT_SECRET'),
|
|
92
|
+
callbackUrl: config.get('GOOGLE_CALLBACK_URL'),
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'line',
|
|
96
|
+
provider: LineOAuthProvider,
|
|
97
|
+
clientId: config.get('LINE_CLIENT_ID'),
|
|
98
|
+
clientSecret: config.get('LINE_CLIENT_SECRET'),
|
|
99
|
+
callbackUrl: config.get('LINE_CALLBACK_URL'),
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
}),
|
|
103
|
+
inject: [ConfigService],
|
|
104
|
+
}),
|
|
105
|
+
],
|
|
106
|
+
})
|
|
107
|
+
export class AppModule {}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## REST API
|
|
111
|
+
|
|
112
|
+
All endpoints are served under the `/oauth` prefix.
|
|
113
|
+
|
|
114
|
+
### Redirect to provider
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
GET /oauth/:provider
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Generates a CSRF state token, stores it, and issues a `302` redirect to the provider's authorization URL.
|
|
121
|
+
|
|
122
|
+
| Parameter | Where | Description |
|
|
123
|
+
|-----------|-------|-------------|
|
|
124
|
+
| `provider` | path | Provider name as registered in `providers` (e.g. `google`, `line`) |
|
|
125
|
+
|
|
126
|
+
**Response:** `302 Found` — Location header points to the provider's authorization URL.
|
|
127
|
+
|
|
128
|
+
### Handle callback
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
GET /oauth/:provider/callback?code=...&state=...
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Verifies the state, exchanges the authorization code for tokens, fetches the user profile, and returns it.
|
|
135
|
+
|
|
136
|
+
| Parameter | Where | Description |
|
|
137
|
+
|-----------|-------|-------------|
|
|
138
|
+
| `provider` | path | Provider name |
|
|
139
|
+
| `code` | query | Authorization code from the provider |
|
|
140
|
+
| `state` | query | CSRF state value returned by the provider |
|
|
141
|
+
|
|
142
|
+
**Response without `callbackSuccessUrl`:** `200 OK`
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"provider": "google",
|
|
147
|
+
"providerId": "1234567890",
|
|
148
|
+
"email": "user@example.com",
|
|
149
|
+
"name": "Jane Doe",
|
|
150
|
+
"avatarUrl": "https://example.com/avatar.jpg"
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Fields `email`, `name`, and `avatarUrl` may be `null` depending on the scopes granted and the provider's response.
|
|
155
|
+
|
|
156
|
+
**Response with `callbackSuccessUrl`:** `302 Found` — redirects to `callbackSuccessUrl` with the profile fields appended as query parameters (`provider`, `providerId`, `email`, `name`, `avatarUrl`).
|
|
157
|
+
|
|
158
|
+
## Built-in Providers
|
|
159
|
+
|
|
160
|
+
### GoogleOAuthProvider
|
|
161
|
+
|
|
162
|
+
Default scopes: `openid profile email`
|
|
163
|
+
|
|
164
|
+
Verifies the `id_token` using the Google auth library when present; falls back to the UserInfo endpoint otherwise.
|
|
165
|
+
|
|
166
|
+
### LineOAuthProvider
|
|
167
|
+
|
|
168
|
+
Default scopes: `openid profile`
|
|
169
|
+
|
|
170
|
+
Verifies the `id_token` via LINE's `/oauth2/v2.1/verify` endpoint when present; falls back to the UserInfo endpoint otherwise.
|
|
171
|
+
|
|
172
|
+
Both classes accept a custom `scopes` array to override the defaults.
|
|
173
|
+
|
|
174
|
+
## Architecture
|
|
175
|
+
|
|
176
|
+
### Port / Token Mapping
|
|
177
|
+
|
|
178
|
+
| DI Token | Interface | Default Implementation | Purpose |
|
|
179
|
+
|----------|-----------|------------------------|---------|
|
|
180
|
+
| `'GetAuthorizationUrlPort'` | `GetAuthorizationUrlPort` | `GetAuthorizationUrlUseCase` | Generate a provider authorization URL with a fresh state token |
|
|
181
|
+
| `'HandleCallbackPort'` | `HandleCallbackPort` | `HandleCallbackUseCase` | Verify state, exchange code, return `OAuthProfile` |
|
|
182
|
+
| `'OAuthProviderRegistry'` | `OAuthProviderRegistry` (`Map<string, OAuthProviderPort>`) | Built from `providers` option | Registry of provider name → provider instance |
|
|
183
|
+
| `'OAuthStateStorePort'` | `OAuthStateStorePort` | `InMemoryStateStore` | Generate and verify CSRF state tokens |
|
|
184
|
+
|
|
185
|
+
`GetAuthorizationUrlPort` and `HandleCallbackPort` are exported from the module.
|
|
186
|
+
|
|
187
|
+
## Domain Model
|
|
188
|
+
|
|
189
|
+
### OAuthProfile
|
|
190
|
+
|
|
191
|
+
| Property | Type | Description |
|
|
192
|
+
|----------|------|-------------|
|
|
193
|
+
| `provider` | `string` | Provider name (e.g. `'google'`) |
|
|
194
|
+
| `providerId` | `string` | Provider-scoped user ID |
|
|
195
|
+
| `email` | `string \| null` | User email, if available |
|
|
196
|
+
| `name` | `string \| null` | Display name, if available |
|
|
197
|
+
| `avatarUrl` | `string \| null` | Profile picture URL, if available |
|
|
198
|
+
| `id` | `string` | Composite key: `provider:providerId` |
|
|
199
|
+
|
|
200
|
+
### OAuthToken
|
|
201
|
+
|
|
202
|
+
| Property | Type | Description |
|
|
203
|
+
|----------|------|-------------|
|
|
204
|
+
| `accessToken` | `string` | Bearer token for API calls |
|
|
205
|
+
| `refreshToken` | `string \| null` | Refresh token, if issued |
|
|
206
|
+
| `expiresAt` | `Date \| null` | Computed from `expiresIn` when not provided directly |
|
|
207
|
+
| `idToken` | `string \| null` | OIDC ID token, if present |
|
|
208
|
+
| `isExpired` | `boolean` | `true` when `expiresAt` is in the past |
|
|
209
|
+
|
|
210
|
+
## Custom Adapters
|
|
211
|
+
|
|
212
|
+
### Custom state store
|
|
213
|
+
|
|
214
|
+
Provide a class or instance that implements `OAuthStateStorePort` for persistent CSRF state (e.g. Redis):
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import { Injectable } from '@nestjs/common';
|
|
218
|
+
import { OAuthStateStorePort } from '@devoven/oauth';
|
|
219
|
+
|
|
220
|
+
@Injectable()
|
|
221
|
+
export class RedisStateStore implements OAuthStateStorePort {
|
|
222
|
+
async generate(): Promise<string> {
|
|
223
|
+
const state = crypto.randomUUID();
|
|
224
|
+
await redis.set(`oauth:state:${state}`, '1', 'EX', 300);
|
|
225
|
+
return state;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async verify(state: string): Promise<boolean> {
|
|
229
|
+
const key = `oauth:state:${state}`;
|
|
230
|
+
const exists = await redis.del(key);
|
|
231
|
+
return exists === 1;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
OAuthModule.register({
|
|
238
|
+
providers: [...],
|
|
239
|
+
stateStore: RedisStateStore,
|
|
240
|
+
})
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Custom provider
|
|
244
|
+
|
|
245
|
+
Implement `OAuthProviderPort` to add any OAuth 2.0 / OIDC provider:
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { OAuthProviderPort, OAuthProviderConfig, OAuthProfile, OAuthToken } from '@devoven/oauth';
|
|
249
|
+
|
|
250
|
+
export class GitHubOAuthProvider implements OAuthProviderPort {
|
|
251
|
+
readonly name = 'github';
|
|
252
|
+
|
|
253
|
+
constructor(private readonly config: OAuthProviderConfig) {}
|
|
254
|
+
|
|
255
|
+
async getAuthorizationUrl(state: string): Promise<string> { /* ... */ }
|
|
256
|
+
async exchangeCode(code: string): Promise<OAuthToken> { /* ... */ }
|
|
257
|
+
async getProfile(token: OAuthToken): Promise<OAuthProfile> { /* ... */ }
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Pass it either as a config object (the module will construct it) or as an already-instantiated object:
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
// Config object — module constructs the instance
|
|
265
|
+
OAuthModule.register({
|
|
266
|
+
providers: [
|
|
267
|
+
{
|
|
268
|
+
name: 'github',
|
|
269
|
+
provider: GitHubOAuthProvider,
|
|
270
|
+
clientId: '...',
|
|
271
|
+
clientSecret: '...',
|
|
272
|
+
callbackUrl: 'https://example.com/oauth/github/callback',
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
// Pre-constructed instance
|
|
278
|
+
OAuthModule.register({
|
|
279
|
+
providers: [new GitHubOAuthProvider({ clientId: '...', clientSecret: '...', callbackUrl: '...', scopes: [] })],
|
|
280
|
+
})
|
|
281
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-auth-url.port.js","sourceRoot":"","sources":["../../../src/application/ports/get-auth-url.port.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handle-callback.port.js","sourceRoot":"","sources":["../../../src/application/ports/handle-callback.port.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { OAuthProfile } from "../../domain/entities/oauth-profile.entity";
|
|
2
|
+
import { OAuthToken } from "../../domain/value-objects/oauth-token.vo";
|
|
3
|
+
export interface OAuthProviderPort {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
getAuthorizationUrl(state: string): Promise<string>;
|
|
6
|
+
exchangeCode(code: string): Promise<OAuthToken>;
|
|
7
|
+
getProfile(token: OAuthToken): Promise<OAuthProfile>;
|
|
8
|
+
}
|
|
9
|
+
export interface OAuthProviderConfig {
|
|
10
|
+
clientId: string;
|
|
11
|
+
clientSecret: string;
|
|
12
|
+
callbackUrl: string;
|
|
13
|
+
scopes: string[];
|
|
14
|
+
}
|
|
15
|
+
export type OAuthProviderRegistry = Map<string, OAuthProviderPort>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-provider.port.js","sourceRoot":"","sources":["../../../src/application/ports/oauth-provider.port.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-state-store.port.js","sourceRoot":"","sources":["../../../src/application/ports/oauth-state-store.port.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { OAuthProviderRegistry } from '../ports/oauth-provider.port';
|
|
2
|
+
import { OAuthStateStorePort } from '../ports/oauth-state-store.port';
|
|
3
|
+
import { GetAuthorizationUrlPort } from '../ports/get-auth-url.port';
|
|
4
|
+
export declare class GetAuthorizationUrlUseCase implements GetAuthorizationUrlPort {
|
|
5
|
+
private readonly providers;
|
|
6
|
+
private readonly stateStore;
|
|
7
|
+
constructor(providers: OAuthProviderRegistry, stateStore: OAuthStateStorePort);
|
|
8
|
+
execute(provider: string): Promise<string>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.GetAuthorizationUrlUseCase = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
let GetAuthorizationUrlUseCase = class GetAuthorizationUrlUseCase {
|
|
18
|
+
constructor(providers, stateStore) {
|
|
19
|
+
this.providers = providers;
|
|
20
|
+
this.stateStore = stateStore;
|
|
21
|
+
}
|
|
22
|
+
async execute(provider) {
|
|
23
|
+
const oauthProvider = this.providers.get(provider);
|
|
24
|
+
if (!oauthProvider) {
|
|
25
|
+
throw new Error(`Unknown OAuth provider: ${provider}`);
|
|
26
|
+
}
|
|
27
|
+
const state = await this.stateStore.generate();
|
|
28
|
+
return oauthProvider.getAuthorizationUrl(state);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
exports.GetAuthorizationUrlUseCase = GetAuthorizationUrlUseCase;
|
|
32
|
+
exports.GetAuthorizationUrlUseCase = GetAuthorizationUrlUseCase = __decorate([
|
|
33
|
+
(0, common_1.Injectable)(),
|
|
34
|
+
__param(0, (0, common_1.Inject)('OAuthProviderRegistry')),
|
|
35
|
+
__param(1, (0, common_1.Inject)('OAuthStateStorePort')),
|
|
36
|
+
__metadata("design:paramtypes", [Object, Object])
|
|
37
|
+
], GetAuthorizationUrlUseCase);
|
|
38
|
+
//# sourceMappingURL=get-auth-url.use-case.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-auth-url.use-case.js","sourceRoot":"","sources":["../../../src/application/use-cases/get-auth-url.use-case.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAmD;AAM5C,IAAM,0BAA0B,GAAhC,MAAM,0BAA0B;IACnC,YAEqB,SAAgC,EAGhC,UAA+B;QAH/B,cAAS,GAAT,SAAS,CAAuB;QAGhC,eAAU,GAAV,UAAU,CAAqB;IAChD,CAAC;IAEL,KAAK,CAAC,OAAO,CAAC,QAAgB;QAC1B,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QAElD,IAAI,CAAC,aAAa,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,EAAE,CAAC,CAAA;QAC1D,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAA;QAE9C,OAAO,aAAa,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAA;IACnD,CAAC;CACJ,CAAA;AApBY,gEAA0B;qCAA1B,0BAA0B;IADtC,IAAA,mBAAU,GAAE;IAGJ,WAAA,IAAA,eAAM,EAAC,uBAAuB,CAAC,CAAA;IAG/B,WAAA,IAAA,eAAM,EAAC,qBAAqB,CAAC,CAAA;;GALzB,0BAA0B,CAoBtC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { HandleCallbackPort } from "../ports/handle-callback.port";
|
|
2
|
+
import { OAuthProviderRegistry } from "../ports/oauth-provider.port";
|
|
3
|
+
import { OAuthStateStorePort } from "../ports/oauth-state-store.port";
|
|
4
|
+
import { OAuthProfile } from "../../domain/entities/oauth-profile.entity";
|
|
5
|
+
export declare class HandleCallbackUseCase implements HandleCallbackPort {
|
|
6
|
+
private readonly providers;
|
|
7
|
+
private readonly stateStore;
|
|
8
|
+
constructor(providers: OAuthProviderRegistry, stateStore: OAuthStateStorePort);
|
|
9
|
+
execute(provider: string, code: string, state: string): Promise<OAuthProfile>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.HandleCallbackUseCase = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
let HandleCallbackUseCase = class HandleCallbackUseCase {
|
|
18
|
+
constructor(providers, stateStore) {
|
|
19
|
+
this.providers = providers;
|
|
20
|
+
this.stateStore = stateStore;
|
|
21
|
+
}
|
|
22
|
+
async execute(provider, code, state) {
|
|
23
|
+
const valid = await this.stateStore.verify(state);
|
|
24
|
+
if (!valid) {
|
|
25
|
+
throw new common_1.BadRequestException('Invalid or expired OAuth state');
|
|
26
|
+
}
|
|
27
|
+
const oauthProvider = this.providers.get(provider);
|
|
28
|
+
if (!oauthProvider) {
|
|
29
|
+
throw new common_1.BadRequestException(`Unknown OAuth provider: ${provider}`);
|
|
30
|
+
}
|
|
31
|
+
const token = await oauthProvider.exchangeCode(code);
|
|
32
|
+
return oauthProvider.getProfile(token);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
exports.HandleCallbackUseCase = HandleCallbackUseCase;
|
|
36
|
+
exports.HandleCallbackUseCase = HandleCallbackUseCase = __decorate([
|
|
37
|
+
(0, common_1.Injectable)(),
|
|
38
|
+
__param(0, (0, common_1.Inject)('OAuthProviderRegistry')),
|
|
39
|
+
__param(1, (0, common_1.Inject)('OAuthStateStorePort')),
|
|
40
|
+
__metadata("design:paramtypes", [Object, Object])
|
|
41
|
+
], HandleCallbackUseCase);
|
|
42
|
+
//# sourceMappingURL=handle-callback.use-case.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handle-callback.use-case.js","sourceRoot":"","sources":["../../../src/application/use-cases/handle-callback.use-case.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAyE;AAOlE,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IAC9B,YAEqB,SAAgC,EAGhC,UAA+B;QAH/B,cAAS,GAAT,SAAS,CAAuB;QAGhC,eAAU,GAAV,UAAU,CAAqB;IAChD,CAAC;IAEL,KAAK,CAAC,OAAO,CAAC,QAAgB,EAAE,IAAY,EAAE,KAAa;QACvD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAEjD,IAAI,CAAC,KAAK,EAAE,CAAC;YACT,MAAM,IAAI,4BAAmB,CAAC,gCAAgC,CAAC,CAAA;QACnE,CAAC;QAED,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QAElD,IAAI,CAAC,aAAa,EAAE,CAAC;YACjB,MAAM,IAAI,4BAAmB,CAAC,2BAA2B,QAAQ,EAAE,CAAC,CAAA;QACxE,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,CAAA;QAEpD,OAAO,aAAa,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;IAC1C,CAAC;CACJ,CAAA;AA1BY,sDAAqB;gCAArB,qBAAqB;IADjC,IAAA,mBAAU,GAAE;IAGJ,WAAA,IAAA,eAAM,EAAC,uBAAuB,CAAC,CAAA;IAG/B,WAAA,IAAA,eAAM,EAAC,qBAAqB,CAAC,CAAA;;GALzB,qBAAqB,CA0BjC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare class OAuthProfile {
|
|
2
|
+
readonly providerId: string;
|
|
3
|
+
readonly provider: string;
|
|
4
|
+
readonly email: string | null;
|
|
5
|
+
readonly name: string | null;
|
|
6
|
+
readonly avatarUrl: string | null;
|
|
7
|
+
private constructor();
|
|
8
|
+
static create(params: {
|
|
9
|
+
providerId: string;
|
|
10
|
+
provider: string;
|
|
11
|
+
email: string | null;
|
|
12
|
+
name: string | null;
|
|
13
|
+
avatarUrl: string | null;
|
|
14
|
+
}): OAuthProfile;
|
|
15
|
+
get id(): string;
|
|
16
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OAuthProfile = void 0;
|
|
4
|
+
class OAuthProfile {
|
|
5
|
+
constructor(params) {
|
|
6
|
+
this.provider = params.provider;
|
|
7
|
+
this.providerId = params.providerId;
|
|
8
|
+
this.email = params.email;
|
|
9
|
+
this.name = params.name;
|
|
10
|
+
this.avatarUrl = params.avatarUrl;
|
|
11
|
+
}
|
|
12
|
+
static create(params) {
|
|
13
|
+
if (!params.providerId) {
|
|
14
|
+
throw new Error('providerId is required');
|
|
15
|
+
}
|
|
16
|
+
if (!params.provider || params.provider.trim().length === 0) {
|
|
17
|
+
throw new Error('provider cannot be empty');
|
|
18
|
+
}
|
|
19
|
+
return new OAuthProfile(params);
|
|
20
|
+
}
|
|
21
|
+
get id() {
|
|
22
|
+
return `${this.provider}:${this.providerId}`;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.OAuthProfile = OAuthProfile;
|
|
26
|
+
//# sourceMappingURL=oauth-profile.entity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-profile.entity.js","sourceRoot":"","sources":["../../../src/domain/entities/oauth-profile.entity.ts"],"names":[],"mappings":";;;AAAA,MAAa,YAAY;IAOrB,YAAoB,MAMnB;QACG,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAA;QAC/B,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,CAAA;QACnC,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAA;QACzB,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAA;QACvB,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAA;IACrC,CAAC;IAED,MAAM,CAAC,MAAM,CAAC,MAMb;QACG,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAA;QAC7C,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1D,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAA;QAC/C,CAAC;QAED,OAAO,IAAI,YAAY,CAAC,MAAM,CAAC,CAAA;IACnC,CAAC;IAED,IAAI,EAAE;QACF,OAAO,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,UAAU,EAAE,CAAA;IAChD,CAAC;CACJ;AA1CD,oCA0CC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare class OAuthToken {
|
|
2
|
+
readonly accessToken: string;
|
|
3
|
+
readonly refreshToken: string | null;
|
|
4
|
+
readonly expiresAt: Date | null;
|
|
5
|
+
readonly idToken: string | null;
|
|
6
|
+
private constructor();
|
|
7
|
+
get isExpired(): boolean;
|
|
8
|
+
static create(params: {
|
|
9
|
+
accessToken: string;
|
|
10
|
+
refreshToken: string | null;
|
|
11
|
+
expiresIn: number | null;
|
|
12
|
+
expiresAt: Date | null;
|
|
13
|
+
idToken: string | null;
|
|
14
|
+
}): OAuthToken;
|
|
15
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OAuthToken = void 0;
|
|
4
|
+
class OAuthToken {
|
|
5
|
+
constructor(accessToken, refreshToken, expiresAt, idToken) {
|
|
6
|
+
this.accessToken = accessToken;
|
|
7
|
+
this.refreshToken = refreshToken;
|
|
8
|
+
this.expiresAt = expiresAt;
|
|
9
|
+
this.idToken = idToken;
|
|
10
|
+
}
|
|
11
|
+
get isExpired() {
|
|
12
|
+
return this.expiresAt ? Date.now() > this.expiresAt.getTime() : false;
|
|
13
|
+
}
|
|
14
|
+
static create(params) {
|
|
15
|
+
if (!params.accessToken) {
|
|
16
|
+
throw new Error("accessToken is required");
|
|
17
|
+
}
|
|
18
|
+
const expiresAt = params.expiresAt ??
|
|
19
|
+
(params.expiresIn
|
|
20
|
+
? new Date(Date.now() + params.expiresIn * 1000)
|
|
21
|
+
: null);
|
|
22
|
+
return new OAuthToken(params.accessToken, params.refreshToken, expiresAt, params.idToken);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.OAuthToken = OAuthToken;
|
|
26
|
+
//# sourceMappingURL=oauth-token.vo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-token.vo.js","sourceRoot":"","sources":["../../../src/domain/value-objects/oauth-token.vo.ts"],"names":[],"mappings":";;;AAAA,MAAa,UAAU;IAMnB,YACI,WAAmB,EACnB,YAA2B,EAC3B,SAAsB,EACtB,OAAsB;QAEtB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAA;QAC9B,IAAI,CAAC,YAAY,GAAG,YAAY,CAAA;QAChC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IAC1B,CAAC;IAED,IAAI,SAAS;QACT,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;IAC1E,CAAC;IAED,MAAM,CAAC,MAAM,CAAC,MAMb;QACG,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAA;QAC9C,CAAC;QAED,MAAM,SAAS,GACX,MAAM,CAAC,SAAS;YAChB,CAAC,MAAM,CAAC,SAAS;gBACb,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC;gBAChD,CAAC,CAAC,IAAI,CAAC,CAAA;QAEf,OAAO,IAAI,UAAU,CACjB,MAAM,CAAC,WAAW,EAClB,MAAM,CAAC,YAAY,EACnB,SAAS,EACT,MAAM,CAAC,OAAO,CACjB,CAAA;IACL,CAAC;CACJ;AA9CD,gCA8CC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { OAuthModule } from './oauth.module';
|
|
2
|
+
export { OAuthModuleOptions, OAuthProviderOption } from './oauth.module-definition';
|
|
3
|
+
export { OAuthProfile } from './domain/entities/oauth-profile.entity';
|
|
4
|
+
export { OAuthToken } from './domain/value-objects/oauth-token.vo';
|
|
5
|
+
export { GetAuthorizationUrlPort } from './application/ports/get-auth-url.port';
|
|
6
|
+
export { HandleCallbackPort } from './application/ports/handle-callback.port';
|
|
7
|
+
export { OAuthProviderPort, OAuthProviderConfig, OAuthProviderRegistry, } from './application/ports/oauth-provider.port';
|
|
8
|
+
export { OAuthStateStorePort } from './application/ports/oauth-state-store.port';
|
|
9
|
+
export { OAuthCallbackQueryDto } from './presentation/dto/oauth-callback-query.dto';
|
|
10
|
+
export { OAuthProfileResponseDto } from './presentation/dto/oauth-profile-response.dto';
|
|
11
|
+
export { GoogleOAuthProvider } from './infrastructure/providers/google-oauth.provider';
|
|
12
|
+
export { LineOAuthProvider } from './infrastructure/providers/line-oauth.provider';
|
|
13
|
+
export { InMemoryStateStore } from './infrastructure/state/in-memory-state-store';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InMemoryStateStore = exports.LineOAuthProvider = exports.GoogleOAuthProvider = exports.OAuthProfileResponseDto = exports.OAuthCallbackQueryDto = exports.OAuthToken = exports.OAuthProfile = exports.OAuthModule = void 0;
|
|
4
|
+
var oauth_module_1 = require("./oauth.module");
|
|
5
|
+
Object.defineProperty(exports, "OAuthModule", { enumerable: true, get: function () { return oauth_module_1.OAuthModule; } });
|
|
6
|
+
var oauth_profile_entity_1 = require("./domain/entities/oauth-profile.entity");
|
|
7
|
+
Object.defineProperty(exports, "OAuthProfile", { enumerable: true, get: function () { return oauth_profile_entity_1.OAuthProfile; } });
|
|
8
|
+
var oauth_token_vo_1 = require("./domain/value-objects/oauth-token.vo");
|
|
9
|
+
Object.defineProperty(exports, "OAuthToken", { enumerable: true, get: function () { return oauth_token_vo_1.OAuthToken; } });
|
|
10
|
+
var oauth_callback_query_dto_1 = require("./presentation/dto/oauth-callback-query.dto");
|
|
11
|
+
Object.defineProperty(exports, "OAuthCallbackQueryDto", { enumerable: true, get: function () { return oauth_callback_query_dto_1.OAuthCallbackQueryDto; } });
|
|
12
|
+
var oauth_profile_response_dto_1 = require("./presentation/dto/oauth-profile-response.dto");
|
|
13
|
+
Object.defineProperty(exports, "OAuthProfileResponseDto", { enumerable: true, get: function () { return oauth_profile_response_dto_1.OAuthProfileResponseDto; } });
|
|
14
|
+
var google_oauth_provider_1 = require("./infrastructure/providers/google-oauth.provider");
|
|
15
|
+
Object.defineProperty(exports, "GoogleOAuthProvider", { enumerable: true, get: function () { return google_oauth_provider_1.GoogleOAuthProvider; } });
|
|
16
|
+
var line_oauth_provider_1 = require("./infrastructure/providers/line-oauth.provider");
|
|
17
|
+
Object.defineProperty(exports, "LineOAuthProvider", { enumerable: true, get: function () { return line_oauth_provider_1.LineOAuthProvider; } });
|
|
18
|
+
var in_memory_state_store_1 = require("./infrastructure/state/in-memory-state-store");
|
|
19
|
+
Object.defineProperty(exports, "InMemoryStateStore", { enumerable: true, get: function () { return in_memory_state_store_1.InMemoryStateStore; } });
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,+CAA4C;AAAnC,2GAAA,WAAW,OAAA;AAGpB,+EAAqE;AAA5D,oHAAA,YAAY,OAAA;AACrB,wEAAkE;AAAzD,4GAAA,UAAU,OAAA;AAWnB,wFAAmF;AAA1E,iIAAA,qBAAqB,OAAA;AAC9B,4FAAuF;AAA9E,qIAAA,uBAAuB,OAAA;AAEhC,0FAAsF;AAA7E,4HAAA,mBAAmB,OAAA;AAC5B,sFAAkF;AAAzE,wHAAA,iBAAiB,OAAA;AAC1B,sFAAiF;AAAxE,2HAAA,kBAAkB,OAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { OAuthProviderConfig, OAuthProviderPort } from "../../application/ports/oauth-provider.port";
|
|
2
|
+
import { OAuthProfile } from "../../domain/entities/oauth-profile.entity";
|
|
3
|
+
import { OAuthToken } from "../../domain/value-objects/oauth-token.vo";
|
|
4
|
+
export declare class GoogleOAuthProvider<T extends OAuthProviderConfig = OAuthProviderConfig> implements OAuthProviderPort {
|
|
5
|
+
readonly name = "google";
|
|
6
|
+
private readonly AUTHORIZE_URL;
|
|
7
|
+
private readonly TOKEN_URL;
|
|
8
|
+
private readonly USERINFO_URL;
|
|
9
|
+
private readonly authClient;
|
|
10
|
+
protected readonly props: T;
|
|
11
|
+
constructor(props: T);
|
|
12
|
+
getAuthorizationUrl(state: string): Promise<string>;
|
|
13
|
+
exchangeCode(code: string): Promise<OAuthToken>;
|
|
14
|
+
getProfile(token: OAuthToken): Promise<OAuthProfile>;
|
|
15
|
+
}
|