Skip to content

Commit 157e36e

Browse files
implement identity client body
1 parent 6ec2d7e commit 157e36e

File tree

5 files changed

+417
-31
lines changed

5 files changed

+417
-31
lines changed
Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,82 @@
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 IdentityBaseClient {
69
abstract requestAndPollForAccessToken(_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

1617
clientId(): string {
17-
return ''
18+
const environment = serviceEnvironment()
19+
if (environment === Environment.Local) {
20+
return 'e5380e02-312a-7408-5718-e07017e9cf52'
21+
} else if (environment === Environment.Production) {
22+
return 'fbdb2649-e327-4907-8f67-908d24cfd7e3'
23+
} else {
24+
return 'e5380e02-312a-7408-5718-e07017e9cf52'
25+
}
1826
}
1927

20-
applicationId(_api: API): string {
21-
return ''
28+
applicationId(api: API): string {
29+
switch (api) {
30+
case 'admin': {
31+
const environment = serviceEnvironment()
32+
if (environment === Environment.Local) {
33+
return 'e92482cebb9bfb9fb5a0199cc770fde3de6c8d16b798ee73e36c9d815e070e52'
34+
} else if (environment === Environment.Production) {
35+
return '7ee65a63608843c577db8b23c4d7316ea0a01bd2f7594f8a9c06ea668c1b775c'
36+
} else {
37+
return 'e92482cebb9bfb9fb5a0199cc770fde3de6c8d16b798ee73e36c9d815e070e52'
38+
}
39+
}
40+
case 'partners': {
41+
const environment = serviceEnvironment()
42+
if (environment === Environment.Local) {
43+
return 'df89d73339ac3c6c5f0a98d9ca93260763e384d51d6038da129889c308973978'
44+
} else if (environment === Environment.Production) {
45+
return '271e16d403dfa18082ffb3d197bd2b5f4479c3fc32736d69296829cbb28d41a6'
46+
} else {
47+
return 'df89d73339ac3c6c5f0a98d9ca93260763e384d51d6038da129889c308973978'
48+
}
49+
}
50+
case 'storefront-renderer': {
51+
const environment = serviceEnvironment()
52+
if (environment === Environment.Local) {
53+
return '46f603de-894f-488d-9471-5b721280ff49'
54+
} else if (environment === Environment.Production) {
55+
return 'ee139b3d-5861-4d45-b387-1bc3ada7811c'
56+
} else {
57+
return '46f603de-894f-488d-9471-5b721280ff49'
58+
}
59+
}
60+
case 'business-platform': {
61+
const environment = serviceEnvironment()
62+
if (environment === Environment.Local) {
63+
return 'ace6dc89-b526-456d-a942-4b8ef6acda4b'
64+
} else if (environment === Environment.Production) {
65+
return '32ff8ee5-82b8-4d93-9f8a-c6997cefb7dc'
66+
} else {
67+
return 'ace6dc89-b526-456d-a942-4b8ef6acda4b'
68+
}
69+
}
70+
case 'app-management': {
71+
const environment = serviceEnvironment()
72+
if (environment === Environment.Production) {
73+
return '7ee65a63608843c577db8b23c4d7316ea0a01bd2f7594f8a9c06ea668c1b775c'
74+
} else {
75+
return 'e92482cebb9bfb9fb5a0199cc770fde3de6c8d16b798ee73e36c9d815e070e52'
76+
}
77+
}
78+
default:
79+
throw new BugError(`Application id for API of type: ${api}`)
80+
}
2281
}
2382
}
Lines changed: 144 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,161 @@
11
import {IdentityBaseClient} from './identity-base-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 IdentityBaseClient {
9+
private readonly mockUserId = '08978734-325e-44ce-bc65-34823a8d5180'
10+
private readonly mockSessionId = 'df63c65c-3731-48af-a28d-72ab16a6523a'
11+
private readonly mockDeviceUuid = '8ba644c8-7d2f-4260-9311-86df09195ee8'
12+
private readonly authTokenPrefix = 'mtkn_'
13+
614
async requestAndPollForAccessToken(_scopes: string[]): Promise<IdentityToken> {
7-
return {} as IdentityToken
15+
const now = this.getCurrentUnixTimestamp()
16+
const exp = now + 7200
17+
const scopes = allDefaultScopes()
18+
19+
const identityTokenPayload = {
20+
client_id: this.clientId(),
21+
token_type: 'SLAT',
22+
exp,
23+
iat: now,
24+
sub: this.mockUserId,
25+
iss: 'https://identity.shop.dev',
26+
sid: this.mockSessionId,
27+
auth_time: now,
28+
amr: ['pwd', 'device-auth'],
29+
device_uuid: this.mockDeviceUuid,
30+
scope: scopes.join(' '),
31+
atl: 1.0,
32+
}
33+
34+
const refreshTokenPayload = {
35+
...identityTokenPayload,
36+
token_use: 'refresh',
37+
}
38+
39+
return Promise.resolve({
40+
accessToken: `${this.authTokenPrefix}${this.encodeTokenPayload(identityTokenPayload)}`,
41+
alias: '',
42+
expiresAt: this.getFutureDate(1),
43+
refreshToken: `${this.authTokenPrefix}${this.encodeTokenPayload(refreshTokenPayload)}`,
44+
scopes,
45+
userId: this.mockUserId,
46+
})
847
}
948

1049
async exchangeAccessForApplicationTokens(
1150
_identityToken: IdentityToken,
1251
_scopes: ExchangeScopes,
1352
_store?: string,
1453
): Promise<{[x: string]: ApplicationToken}> {
15-
return {}
54+
const now = this.getCurrentUnixTimestamp()
55+
const exp = now + 7200
56+
57+
const generateAppToken = (appId: string): ApplicationToken => {
58+
const tokenPayload = {
59+
act: {
60+
iss: 'https://identity.shop.dev',
61+
sub: this.clientId(),
62+
},
63+
aud: appId,
64+
client_id: this.clientId(),
65+
token_type: 'SLAT',
66+
exp,
67+
iat: now,
68+
iss: 'https://identity.shop.dev',
69+
scope: allDefaultScopes().join(' '),
70+
sub: this.mockUserId,
71+
sid: this.mockSessionId,
72+
auth_time: now,
73+
amr: ['pwd', 'device-auth'],
74+
device_uuid: this.mockDeviceUuid,
75+
atl: 1.0,
76+
}
77+
78+
return {
79+
accessToken: `${this.authTokenPrefix}${this.encodeTokenPayload(tokenPayload)}`,
80+
expiresAt: new Date(exp * 1000),
81+
scopes: allDefaultScopes(),
82+
}
83+
}
84+
85+
return {
86+
[applicationId('app-management')]: generateAppToken(applicationId('app-management')),
87+
[applicationId('business-platform')]: generateAppToken(applicationId('business-platform')),
88+
[applicationId('admin')]: generateAppToken(applicationId('admin')),
89+
[applicationId('partners')]: generateAppToken(applicationId('partners')),
90+
[applicationId('storefront-renderer')]: generateAppToken(applicationId('storefront-renderer')),
91+
}
92+
}
93+
94+
async tokenRequest(_params: {
95+
[key: string]: string
96+
}): Promise<Result<TokenRequestResult, {error: string; store?: string}>> {
97+
const token = await this.refreshAccessToken({} as IdentityToken)
98+
return ok({
99+
access_token: token.accessToken,
100+
expires_in: this.getFutureDate(1).getTime(),
101+
refresh_token: token.refreshToken,
102+
scope: allDefaultScopes().join(' '),
103+
})
16104
}
17105

18106
async refreshAccessToken(_currentToken: IdentityToken): Promise<IdentityToken> {
19-
return {} as IdentityToken
107+
const now = this.getCurrentUnixTimestamp()
108+
const exp = now + 7200
109+
110+
const identityTokenPayload = {
111+
client_id: this.clientId(),
112+
token_type: 'SLAT',
113+
exp,
114+
iat: now,
115+
sub: this.mockUserId,
116+
iss: 'https://identity.shop.dev',
117+
sid: this.mockSessionId,
118+
auth_time: now,
119+
amr: ['pwd', 'device-auth'],
120+
device_uuid: this.mockDeviceUuid,
121+
scope: allDefaultScopes().join(' '),
122+
atl: 1.0,
123+
}
124+
125+
const refreshTokenPayload = {
126+
...identityTokenPayload,
127+
token_use: 'refresh',
128+
}
129+
130+
return Promise.resolve({
131+
accessToken: `${this.authTokenPrefix}${this.encodeTokenPayload(identityTokenPayload)}`,
132+
133+
expiresAt: this.getFutureDate(1),
134+
refreshToken: `${this.authTokenPrefix}${this.encodeTokenPayload(refreshTokenPayload)}`,
135+
scopes: allDefaultScopes(),
136+
userId: this.mockUserId,
137+
})
138+
}
139+
140+
clientId(): string {
141+
return 'shopify-cli-development'
142+
}
143+
144+
private getFutureDate(daysInFuture = 100): Date {
145+
const futureDate = new Date()
146+
futureDate.setDate(futureDate.getDate() + daysInFuture)
147+
return futureDate
148+
}
149+
150+
private getCurrentUnixTimestamp(): number {
151+
return Math.floor(Date.now() / 1000)
152+
}
153+
154+
private encodeTokenPayload(payload: object): string {
155+
return Buffer.from(JSON.stringify(payload))
156+
.toString('base64')
157+
.replace(/[=]/g, '')
158+
.replace(/\+/g, '-')
159+
.replace(/\//g, '_')
20160
}
21161
}

0 commit comments

Comments
 (0)