Skip to content

Commit c83c012

Browse files
implement identity client body
1 parent bd49456 commit c83c012

File tree

5 files changed

+383
-33
lines changed

5 files changed

+383
-33
lines changed
Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,73 @@
1-
import {ApplicationToken, IdentityToken} from '../../session/schema.js'
2-
import {ExchangeScopes} from '../../session/exchange.js'
1+
import {IdentityToken} from '../../session/schema.js'
2+
import {TokenRequestResult} from '../../session/exchange.js'
33
import {API} from '../../api.js'
4+
import {Environment, serviceEnvironment} from '../../context/service.js'
5+
import {BugError} from '../../../../public/node/error.js'
6+
import {Result} from '../../../../public/node/result.js'
47

58
export abstract class IdentityClient {
69
abstract requestAccessToken(scopes: string[]): Promise<IdentityToken>
710

8-
abstract exchangeAccessForApplicationTokens(
9-
identityToken: IdentityToken,
10-
scopes: ExchangeScopes,
11-
store?: string,
12-
): Promise<{[x: string]: ApplicationToken}>
11+
abstract tokenRequest(params: {
12+
[key: string]: string
13+
}): Promise<Result<TokenRequestResult, {error: string; store?: string}>>
1314

1415
abstract refreshAccessToken(currentToken: IdentityToken): Promise<IdentityToken>
1516

16-
clientId(): string {
17-
return ''
18-
}
17+
abstract clientId(): string
1918

20-
applicationId(_api: API): string {
21-
return ''
19+
applicationId(api: API): string {
20+
switch (api) {
21+
case 'admin': {
22+
const environment = serviceEnvironment()
23+
if (environment === Environment.Local) {
24+
return 'e92482cebb9bfb9fb5a0199cc770fde3de6c8d16b798ee73e36c9d815e070e52'
25+
} else if (environment === Environment.Production) {
26+
return '7ee65a63608843c577db8b23c4d7316ea0a01bd2f7594f8a9c06ea668c1b775c'
27+
} else {
28+
return 'e92482cebb9bfb9fb5a0199cc770fde3de6c8d16b798ee73e36c9d815e070e52'
29+
}
30+
}
31+
case 'partners': {
32+
const environment = serviceEnvironment()
33+
if (environment === Environment.Local) {
34+
return 'df89d73339ac3c6c5f0a98d9ca93260763e384d51d6038da129889c308973978'
35+
} else if (environment === Environment.Production) {
36+
return '271e16d403dfa18082ffb3d197bd2b5f4479c3fc32736d69296829cbb28d41a6'
37+
} else {
38+
return 'df89d73339ac3c6c5f0a98d9ca93260763e384d51d6038da129889c308973978'
39+
}
40+
}
41+
case 'storefront-renderer': {
42+
const environment = serviceEnvironment()
43+
if (environment === Environment.Local) {
44+
return '46f603de-894f-488d-9471-5b721280ff49'
45+
} else if (environment === Environment.Production) {
46+
return 'ee139b3d-5861-4d45-b387-1bc3ada7811c'
47+
} else {
48+
return '46f603de-894f-488d-9471-5b721280ff49'
49+
}
50+
}
51+
case 'business-platform': {
52+
const environment = serviceEnvironment()
53+
if (environment === Environment.Local) {
54+
return 'ace6dc89-b526-456d-a942-4b8ef6acda4b'
55+
} else if (environment === Environment.Production) {
56+
return '32ff8ee5-82b8-4d93-9f8a-c6997cefb7dc'
57+
} else {
58+
return 'ace6dc89-b526-456d-a942-4b8ef6acda4b'
59+
}
60+
}
61+
case 'app-management': {
62+
const environment = serviceEnvironment()
63+
if (environment === Environment.Production) {
64+
return '7ee65a63608843c577db8b23c4d7316ea0a01bd2f7594f8a9c06ea668c1b775c'
65+
} else {
66+
return 'e92482cebb9bfb9fb5a0199cc770fde3de6c8d16b798ee73e36c9d815e070e52'
67+
}
68+
}
69+
default:
70+
throw new BugError(`Application id for API of type: ${api}`)
71+
}
2272
}
2373
}
Lines changed: 105 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,122 @@
11
import {IdentityClient} from './identity-client.js'
22
import {ApplicationToken, IdentityToken} from '../../session/schema.js'
3-
import {ExchangeScopes} from '../../session/exchange.js'
3+
import {ExchangeScopes, TokenRequestResult} from '../../session/exchange.js'
4+
import {ok, Result} from '../../../../public/node/result.js'
5+
import {allDefaultScopes} from '../../session/scopes.js'
6+
import {applicationId} from '../../session/identity.js'
47

58
export class IdentityMockClient extends IdentityClient {
9+
private readonly mockUserId = '08978734-325e-44ce-bc65-34823a8d5180'
10+
private readonly authTokenPrefix = 'mtkn_'
11+
612
async requestAccessToken(_scopes: string[]): Promise<IdentityToken> {
7-
return {} as IdentityToken
13+
const tokens = this.generateTokens('identity')
14+
15+
return Promise.resolve({
16+
accessToken: tokens.accessToken,
17+
alias: '',
18+
expiresAt: this.getFutureDate(1),
19+
refreshToken: tokens.refreshToken,
20+
scopes: allDefaultScopes(),
21+
userId: this.mockUserId,
22+
})
823
}
924

1025
async exchangeAccessForApplicationTokens(
1126
_identityToken: IdentityToken,
1227
_scopes: ExchangeScopes,
1328
_store?: string,
1429
): Promise<{[x: string]: ApplicationToken}> {
15-
return {}
30+
return {
31+
[applicationId('app-management')]: this.generateTokens(applicationId('app-management')),
32+
[applicationId('business-platform')]: this.generateTokens(applicationId('business-platform')),
33+
[applicationId('admin')]: this.generateTokens(applicationId('admin')),
34+
[applicationId('partners')]: this.generateTokens(applicationId('partners')),
35+
[applicationId('storefront-renderer')]: this.generateTokens(applicationId('storefront-renderer')),
36+
}
37+
}
38+
39+
async tokenRequest(params: {
40+
[key: string]: string
41+
}): Promise<Result<TokenRequestResult, {error: string; store?: string}>> {
42+
const tokens = this.generateTokens(params?.audience ?? '')
43+
return ok({
44+
access_token: tokens.accessToken,
45+
expires_in: this.getFutureDate(1).getTime(),
46+
refresh_token: tokens.refreshToken,
47+
scope: allDefaultScopes().join(' '),
48+
})
1649
}
1750

1851
async refreshAccessToken(_currentToken: IdentityToken): Promise<IdentityToken> {
19-
return {} as IdentityToken
52+
const tokens = this.generateTokens('identity')
53+
54+
return Promise.resolve({
55+
accessToken: tokens.accessToken,
56+
alias: 'dev@shopify.com',
57+
expiresAt: this.getFutureDate(1),
58+
refreshToken: tokens.refreshToken,
59+
scopes: allDefaultScopes(),
60+
userId: this.mockUserId,
61+
})
62+
}
63+
64+
clientId(): string {
65+
return 'shopify-cli-development'
66+
}
67+
68+
private readonly generateTokens = (appId: string) => {
69+
const now = this.getCurrentUnixTimestamp()
70+
const exp = now + 7200
71+
72+
const tokenPayload = {
73+
act: {
74+
iss: 'https://identity.shop.dev',
75+
sub: this.clientId(),
76+
},
77+
aud: appId,
78+
client_id: this.clientId(),
79+
token_type: 'SLAT',
80+
exp,
81+
iat: now,
82+
iss: 'https://identity.shop.dev',
83+
scope: allDefaultScopes().join(' '),
84+
sub: this.mockUserId,
85+
sid: 'df63c65c-3731-48af-a28d-72ab16a6523a',
86+
auth_time: now,
87+
amr: ['pwd', 'device-auth'],
88+
device_uuid: '8ba644c8-7d2f-4260-9311-86df09195ee8',
89+
atl: 1.0,
90+
}
91+
92+
const refreshTokenPayload = {
93+
...tokenPayload,
94+
token_use: 'refresh',
95+
}
96+
97+
return {
98+
accessToken: `${this.authTokenPrefix}${this.encodeTokenPayload(tokenPayload)}`,
99+
refreshToken: `${this.authTokenPrefix}${this.encodeTokenPayload(refreshTokenPayload)}`,
100+
expiresAt: new Date(exp * 1000),
101+
scopes: allDefaultScopes(),
102+
}
103+
}
104+
105+
private getFutureDate(daysInFuture = 100): Date {
106+
const futureDate = new Date()
107+
futureDate.setDate(futureDate.getDate() + daysInFuture)
108+
return futureDate
109+
}
110+
111+
private getCurrentUnixTimestamp(): number {
112+
return Math.floor(Date.now() / 1000)
113+
}
114+
115+
private encodeTokenPayload(payload: object): string {
116+
return Buffer.from(JSON.stringify(payload))
117+
.toString('base64')
118+
.replace(/[=]/g, '')
119+
.replace(/\+/g, '-')
120+
.replace(/\//g, '_')
20121
}
21122
}

0 commit comments

Comments
 (0)