@strav/social 1.0.0-alpha.22 → 1.0.0-alpha.24
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/package.json +7 -7
- package/src/{facebook → drivers/facebook}/facebook_config.ts +1 -1
- package/src/{facebook → drivers/facebook}/facebook_driver.ts +5 -5
- package/src/{facebook → drivers/facebook}/facebook_provider.ts +2 -2
- package/src/{google → drivers/google}/google_config.ts +1 -1
- package/src/{google → drivers/google}/google_driver.ts +5 -5
- package/src/{google → drivers/google}/google_provider.ts +2 -2
- package/src/{line → drivers/line}/line_config.ts +1 -1
- package/src/{line → drivers/line}/line_driver.ts +5 -5
- package/src/{line → drivers/line}/line_provider.ts +2 -2
- package/src/ledger/social_account_repository.ts +2 -15
- package/src/social_driver.ts +42 -4
- package/src/tenanted/tenanted_social_account_repository.ts +2 -15
- /package/src/{facebook → drivers/facebook}/index.ts +0 -0
- /package/src/{google → drivers/google}/index.ts +0 -0
- /package/src/{line → drivers/line}/index.ts +0 -0
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strav/social",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.24",
|
|
4
4
|
"description": "Strav social-login module — provider-agnostic OAuth/OIDC client. Normalized profile + token DTOs, state + PKCE helpers, capability gating, multi-provider routing. Line / Google / Facebook adapters ship as subpath imports (`@strav/social/line`, `@strav/social/google`, `@strav/social/facebook`).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
7
7
|
"types": "./src/index.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": "./src/index.ts",
|
|
10
|
-
"./line": "./src/line/index.ts",
|
|
11
|
-
"./google": "./src/google/index.ts",
|
|
12
|
-
"./facebook": "./src/facebook/index.ts",
|
|
10
|
+
"./line": "./src/drivers/line/index.ts",
|
|
11
|
+
"./google": "./src/drivers/google/index.ts",
|
|
12
|
+
"./facebook": "./src/drivers/facebook/index.ts",
|
|
13
13
|
"./tenanted": "./src/tenanted/index.ts"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"access": "public"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@strav/database": "1.0.0-alpha.
|
|
27
|
-
"@strav/http": "1.0.0-alpha.
|
|
28
|
-
"@strav/kernel": "1.0.0-alpha.
|
|
26
|
+
"@strav/database": "1.0.0-alpha.24",
|
|
27
|
+
"@strav/http": "1.0.0-alpha.24",
|
|
28
|
+
"@strav/kernel": "1.0.0-alpha.24"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"@types/bun": ">=1.3.14"
|
|
@@ -33,23 +33,23 @@
|
|
|
33
33
|
* Graph API directly.
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
|
-
import type { OAuthTokens, SocialProfile } from '
|
|
37
|
-
import type { SocialCapability } from '
|
|
36
|
+
import type { OAuthTokens, SocialProfile } from '../../dto/index.ts'
|
|
37
|
+
import type { SocialCapability } from '../../social_capabilities.ts'
|
|
38
38
|
import type {
|
|
39
39
|
AuthorizeInput,
|
|
40
40
|
AuthorizeResult,
|
|
41
41
|
ExchangeInput,
|
|
42
42
|
RefreshInput,
|
|
43
43
|
SocialDriver,
|
|
44
|
-
} from '
|
|
44
|
+
} from '../../social_driver.ts'
|
|
45
45
|
import {
|
|
46
46
|
InvalidTokenError,
|
|
47
47
|
OAuthExchangeError,
|
|
48
48
|
ProviderUnsupportedError,
|
|
49
49
|
SocialProviderError,
|
|
50
50
|
StateMismatchError,
|
|
51
|
-
} from '
|
|
52
|
-
import { codeChallengeFor, randomCodeVerifier, randomState } from '
|
|
51
|
+
} from '../../social_error.ts'
|
|
52
|
+
import { codeChallengeFor, randomCodeVerifier, randomState } from '../../pkce.ts'
|
|
53
53
|
import {
|
|
54
54
|
DEFAULT_FACEBOOK_PROFILE_FIELDS,
|
|
55
55
|
facebookEndpoints,
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { type Application, ServiceProvider } from '@strav/kernel'
|
|
7
|
-
import { SocialConfigError } from '
|
|
8
|
-
import { SocialManager } from '
|
|
7
|
+
import { SocialConfigError } from '../../social_error.ts'
|
|
8
|
+
import { SocialManager } from '../../social_manager.ts'
|
|
9
9
|
import type { FacebookProviderConfig } from './facebook_config.ts'
|
|
10
10
|
import { FacebookSocialDriver } from './facebook_driver.ts'
|
|
11
11
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* enforced per-authorize via `authorize({ extra: { hd: 'example.com' } })`.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import type { ProviderConfig } from '
|
|
14
|
+
import type { ProviderConfig } from '../../types.ts'
|
|
15
15
|
|
|
16
16
|
export interface GoogleProviderConfig extends ProviderConfig {
|
|
17
17
|
driver: 'google'
|
|
@@ -32,22 +32,22 @@
|
|
|
32
32
|
* consent to one Workspace domain.
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
|
-
import type { OAuthTokens, SocialProfile } from '
|
|
36
|
-
import type { SocialCapability } from '
|
|
35
|
+
import type { OAuthTokens, SocialProfile } from '../../dto/index.ts'
|
|
36
|
+
import type { SocialCapability } from '../../social_capabilities.ts'
|
|
37
37
|
import type {
|
|
38
38
|
AuthorizeInput,
|
|
39
39
|
AuthorizeResult,
|
|
40
40
|
ExchangeInput,
|
|
41
41
|
RefreshInput,
|
|
42
42
|
SocialDriver,
|
|
43
|
-
} from '
|
|
43
|
+
} from '../../social_driver.ts'
|
|
44
44
|
import {
|
|
45
45
|
InvalidTokenError,
|
|
46
46
|
OAuthExchangeError,
|
|
47
47
|
SocialProviderError,
|
|
48
48
|
StateMismatchError,
|
|
49
|
-
} from '
|
|
50
|
-
import { codeChallengeFor, randomCodeVerifier, randomState } from '
|
|
49
|
+
} from '../../social_error.ts'
|
|
50
|
+
import { codeChallengeFor, randomCodeVerifier, randomState } from '../../pkce.ts'
|
|
51
51
|
import { GOOGLE_ENDPOINTS, type GoogleProviderConfig } from './google_config.ts'
|
|
52
52
|
|
|
53
53
|
const PROVIDER = 'google'
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { type Application, ServiceProvider } from '@strav/kernel'
|
|
11
|
-
import { SocialConfigError } from '
|
|
12
|
-
import { SocialManager } from '
|
|
11
|
+
import { SocialConfigError } from '../../social_error.ts'
|
|
12
|
+
import { SocialManager } from '../../social_manager.ts'
|
|
13
13
|
import type { GoogleProviderConfig } from './google_config.ts'
|
|
14
14
|
import { GoogleSocialDriver } from './google_driver.ts'
|
|
15
15
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* inside the channel (Line approval required for production).
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type { ProviderConfig } from '
|
|
11
|
+
import type { ProviderConfig } from '../../types.ts'
|
|
12
12
|
|
|
13
13
|
export interface LineProviderConfig extends ProviderConfig {
|
|
14
14
|
driver: 'line'
|
|
@@ -29,22 +29,22 @@
|
|
|
29
29
|
* Token / refresh / revoke all use the standard OAuth2 endpoints.
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
-
import type { OAuthTokens, SocialProfile } from '
|
|
33
|
-
import type { SocialCapability } from '
|
|
32
|
+
import type { OAuthTokens, SocialProfile } from '../../dto/index.ts'
|
|
33
|
+
import type { SocialCapability } from '../../social_capabilities.ts'
|
|
34
34
|
import type {
|
|
35
35
|
AuthorizeInput,
|
|
36
36
|
AuthorizeResult,
|
|
37
37
|
ExchangeInput,
|
|
38
38
|
RefreshInput,
|
|
39
39
|
SocialDriver,
|
|
40
|
-
} from '
|
|
40
|
+
} from '../../social_driver.ts'
|
|
41
41
|
import {
|
|
42
42
|
InvalidTokenError,
|
|
43
43
|
OAuthExchangeError,
|
|
44
44
|
SocialProviderError,
|
|
45
45
|
StateMismatchError,
|
|
46
|
-
} from '
|
|
47
|
-
import { codeChallengeFor, randomCodeVerifier, randomState } from '
|
|
46
|
+
} from '../../social_error.ts'
|
|
47
|
+
import { codeChallengeFor, randomCodeVerifier, randomState } from '../../pkce.ts'
|
|
48
48
|
import { LINE_ENDPOINTS, type LineProviderConfig } from './line_config.ts'
|
|
49
49
|
|
|
50
50
|
const PROVIDER = 'line'
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { type Application, ServiceProvider } from '@strav/kernel'
|
|
12
|
-
import { SocialConfigError } from '
|
|
13
|
-
import { SocialManager } from '
|
|
12
|
+
import { SocialConfigError } from '../../social_error.ts'
|
|
13
|
+
import { SocialManager } from '../../social_manager.ts'
|
|
14
14
|
import type { LineProviderConfig } from './line_config.ts'
|
|
15
15
|
import { LineSocialDriver } from './line_driver.ts'
|
|
16
16
|
|
|
@@ -26,10 +26,8 @@
|
|
|
26
26
|
* encrypt/decrypt throws `ConfigError`.
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
import {
|
|
31
|
-
// biome-ignore lint/style/useImportType: Cipher + EventBus value imports for @inject() metadata.
|
|
32
|
-
import { Cipher, EventBus, inject, ulid } from '@strav/kernel'
|
|
29
|
+
import { quoteIdent, Repository } from '@strav/database'
|
|
30
|
+
import { ulid } from '@strav/kernel'
|
|
33
31
|
import type { OAuthTokens, SocialProfile } from '../dto/index.ts'
|
|
34
32
|
import { SocialAccount } from './social_account.ts'
|
|
35
33
|
import { socialAccountSchema } from './social_account_schema.ts'
|
|
@@ -48,21 +46,10 @@ export interface DisconnectInput {
|
|
|
48
46
|
provider: string
|
|
49
47
|
}
|
|
50
48
|
|
|
51
|
-
@inject()
|
|
52
49
|
export class SocialAccountRepository extends Repository<SocialAccount> {
|
|
53
50
|
static override readonly schema = socialAccountSchema
|
|
54
51
|
static override readonly model = SocialAccount
|
|
55
52
|
|
|
56
|
-
// biome-ignore lint/complexity/noUselessConstructor: explicit constructor forces TS to emit `design:paramtypes` for @inject(). The fourth param is the Cipher for @encrypt token columns — apps must register EncryptionProvider before this repository resolves.
|
|
57
|
-
constructor(
|
|
58
|
-
db: PostgresDatabase,
|
|
59
|
-
events: EventBus,
|
|
60
|
-
registry?: SchemaRegistry,
|
|
61
|
-
cipher?: Cipher,
|
|
62
|
-
) {
|
|
63
|
-
super(db, events, registry, cipher)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
53
|
/**
|
|
67
54
|
* Upsert a social account by `(provider, provider_user_id)`.
|
|
68
55
|
* Insert on first link; update tokens + cached profile fields
|
package/src/social_driver.ts
CHANGED
|
@@ -5,10 +5,48 @@
|
|
|
5
5
|
* (`config.social.providers[name]`). The manager holds one
|
|
6
6
|
* driver per configured name and routes calls into it.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* ## Capability gating — the "honest LSP violation" pattern
|
|
9
|
+
*
|
|
10
|
+
* Strictly speaking, the interface below violates Liskov substitution
|
|
11
|
+
* because `FacebookSocialDriver.refresh()` throws synchronously instead
|
|
12
|
+
* of returning `Promise<OAuthTokens>` (Facebook doesn't issue refresh
|
|
13
|
+
* tokens). Same shape for any future driver that lacks an operation
|
|
14
|
+
* its interface declares.
|
|
15
|
+
*
|
|
16
|
+
* The framework prefers this trade-off over the alternative (every
|
|
17
|
+
* provider gets its own narrowed sub-interface, every app does a
|
|
18
|
+
* `if (isRefreshable(driver))` narrowing dance) for three reasons:
|
|
19
|
+
*
|
|
20
|
+
* 1. **Discoverability**: apps see ONE `SocialDriver` interface
|
|
21
|
+
* with all the operations a sign-in flow needs. They learn the
|
|
22
|
+
* surface once, not per-adapter.
|
|
23
|
+
*
|
|
24
|
+
* 2. **Capability flags are the typed truth**: each driver
|
|
25
|
+
* declares `capabilities: ReadonlySet<SocialCapability>`. Apps
|
|
26
|
+
* that care about portability check the flag and branch — the
|
|
27
|
+
* `unsupported` throw becomes the safety net for callers who
|
|
28
|
+
* forgot, not the primary API.
|
|
29
|
+
*
|
|
30
|
+
* 3. **Failures are loud and synchronous**. Drivers use the
|
|
31
|
+
* `unsupported(provider, op, reason?)` helper so the throw
|
|
32
|
+
* happens on the function call, NOT after a network round-trip
|
|
33
|
+
* had a chance to bill the user / consume rate limit. Apps fail
|
|
34
|
+
* fast.
|
|
35
|
+
*
|
|
36
|
+
* Apps that want compile-time safety against unsupported ops wrap the
|
|
37
|
+
* driver themselves:
|
|
38
|
+
*
|
|
39
|
+
* ```ts
|
|
40
|
+
* function refreshableDriver(d: SocialDriver) {
|
|
41
|
+
* if (!d.capabilities.has('tokens.refresh')) return null
|
|
42
|
+
* return d // narrowed by convention; refresh() is now safe to call.
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* A split into `SocialDriver` + `RefreshableSocialDriver` + ... may
|
|
47
|
+
* land later (`docs/code-quality.md` action item #4) — but doing so
|
|
48
|
+
* carries the manager's `use(name): SocialDriver` return type through
|
|
49
|
+
* a wider refactor. Until then, the capability set IS the contract.
|
|
12
50
|
*/
|
|
13
51
|
|
|
14
52
|
import type { OAuthTokens, SocialProfile } from './dto/index.ts'
|
|
@@ -11,10 +11,8 @@
|
|
|
11
11
|
* branching on a tenancy flag.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
// biome-ignore lint/style/useImportType: Cipher + EventBus value imports for @inject() metadata.
|
|
17
|
-
import { Cipher, EventBus, inject, ulid } from '@strav/kernel'
|
|
14
|
+
import { quoteIdent, Repository } from '@strav/database'
|
|
15
|
+
import { ulid } from '@strav/kernel'
|
|
18
16
|
import type { OAuthTokens, SocialProfile } from '../dto/index.ts'
|
|
19
17
|
import { SocialAccountAlreadyLinkedError } from '../ledger/social_account_repository.ts'
|
|
20
18
|
import { TenantedSocialAccount } from './tenanted_social_account.ts'
|
|
@@ -32,21 +30,10 @@ export interface DisconnectInput {
|
|
|
32
30
|
provider: string
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
@inject()
|
|
36
33
|
export class TenantedSocialAccountRepository extends Repository<TenantedSocialAccount> {
|
|
37
34
|
static override readonly schema = tenantedSocialAccountSchema
|
|
38
35
|
static override readonly model = TenantedSocialAccount
|
|
39
36
|
|
|
40
|
-
// biome-ignore lint/complexity/noUselessConstructor: explicit constructor forces TS to emit `design:paramtypes` for @inject(). The fourth param is the Cipher for @encrypt token columns.
|
|
41
|
-
constructor(
|
|
42
|
-
db: PostgresDatabase,
|
|
43
|
-
events: EventBus,
|
|
44
|
-
registry?: SchemaRegistry,
|
|
45
|
-
cipher?: Cipher,
|
|
46
|
-
) {
|
|
47
|
-
super(db, events, registry, cipher)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
37
|
async connect(input: ConnectInput): Promise<TenantedSocialAccount> {
|
|
51
38
|
const existing = await this.findByProviderIdentity(
|
|
52
39
|
input.provider,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|