Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions packages/cli-kit/src/private/node/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import {IdentityToken, Session, Sessions} from './session/schema.js'
import * as sessionStore from './session/store.js'
import {pollForDeviceAuthorization, requestDeviceAuthorization} from './session/device-authorization.js'
import {shouldUseMockAuth, fetchMockUser, buildMockSession} from './session/mock-auth.js'
import {isThemeAccessSession} from './api/rest.js'
import {getCurrentSessionId, setCurrentSessionId} from './conf-store.js'
import {UserEmailQueryString, UserEmailQuery} from './api/graphql/business-platform-destinations/user-email.js'
Expand Down Expand Up @@ -197,6 +198,7 @@ export async function ensureAuthenticated(
{forceRefresh = false, noPrompt = false, forceNewSession = false}: EnsureAuthenticatedAdditionalOptions = {},
): Promise<OAuthSession> {
const fqdn = await identityFqdn()
// debugger

const previousStoreFqdn = applications.adminApi?.storeFqdn
if (previousStoreFqdn) {
Expand Down Expand Up @@ -301,6 +303,15 @@ async function executeCompleteFlow(applications: OAuthApplications): Promise<Ses
scopes.push('employee')
}

// debugger
if (shouldUseMockAuth()) {
outputDebug(outputContent`Using mock authentication...`)
const mockUser = await fetchMockUser(scopes)
const session = buildMockSession(mockUser, scopes)
outputCompleted(`Logged in with mock user: ${mockUser.userinfo.email}`)
return session
}

let identityToken: IdentityToken
const identityTokenInformation = getIdentityTokenInformation()
if (identityTokenInformation) {
Expand Down
112 changes: 112 additions & 0 deletions packages/cli-kit/src/private/node/session/mock-auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import {IdentityToken, Session} from './schema.js'
import {shopifyFetch} from '../../../public/node/http.js'
import {outputDebug, outputContent, outputToken} from '../../../public/node/output.js'
import {isLocalEnvironment} from '../context/service.js'
import {assertConnectable} from '../../../public/node/vendor/dev_server/network/index.js'
import {developerDashboardFqdn} from '../../../public/node/context/fqdn.js'

interface MockUserResponse {
uuid: string
expires_at: string
id_token: string
access_token: string
refresh_token: string
last_login: string
scope: string
userinfo: {
email: string
email_verified: boolean
given_name: string
family_name: string
name: string
picture: string
zoneinfo: string
locale: string
permissions: string[]
okta_id: string | null
updated_at: string
created_at: string
}
}

export function shouldUseMockAuth(): boolean {
if (!isLocalEnvironment()) {
return false
}

try {
assertConnectable({
projectName: 'identity',
addr: '127.0.0.1',
port: 8080,
timeout: 100,
})
return false
} catch (e) {

Check failure on line 45 in packages/cli-kit/src/private/node/session/mock-auth.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/cli-kit/src/private/node/session/mock-auth.ts#L45

[id-length] Identifier name 'e' is too short (< 2).
outputDebug(outputContent`Identity service is not running. Using mock authentication.`)
throw e
return true
}
}

export async function fetchMockUser(scopes: string[]): Promise<MockUserResponse> {
const devDashboardFqdn = await developerDashboardFqdn()
const scopeParam = scopes.join(' ')
const nonce = Math.random().toString(36).substring(2)
const state = Math.random().toString(36).substring(2)

const params = new URLSearchParams({
'config-key': 'cli',
scope: scopeParam,
nonce,
state,
'created-at': Date.now().toString(),
})

const url = `https://${devDashboardFqdn}/identity/test-login?${params.toString()}`

outputDebug(outputContent`Fetching mock user from: ${url}`)

const response = await shopifyFetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Accept: 'application/json',
},
})

if (!response.ok) {
const text = await response.text()
outputDebug(outputContent`Failed response: ${text.substring(0, 500)}`)
throw new Error(`Failed to fetch mock user: ${response.status} ${response.statusText}`)
}

const data = (await response.json()) as MockUserResponse
outputDebug(outputContent`Mock user fetched: ${outputToken.json(data)}`)

return data
}

export function buildMockIdentityToken(mockUser: MockUserResponse, scopes: string[]): IdentityToken {

Check failure on line 90 in packages/cli-kit/src/private/node/session/mock-auth.ts

View workflow job for this annotation

GitHub Actions / knip-reporter-annotations-check

packages/cli-kit/src/private/node/session/mock-auth.ts#L90

'buildMockIdentityToken' is an unused export
const expiresAt = new Date(mockUser.expires_at)

return {
accessToken: mockUser.access_token,
refreshToken: mockUser.refresh_token,
expiresAt,
scopes,
userId: mockUser.uuid,
}
}

export function buildMockSession(mockUser: MockUserResponse, scopes: string[]): Session {
const identityToken = buildMockIdentityToken(mockUser, scopes)

return {
identity: {
...identityToken,
alias: mockUser.userinfo.email,
},
applications: {},
}
}
3 changes: 3 additions & 0 deletions packages/cli-kit/src/public/node/context/fqdn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {AbortError, BugError} from '../error.js'
import {serviceEnvironment} from '../../../private/node/context/service.js'
import {DevServer, DevServerCore} from '../vendor/dev_server/index.js'
import {blockPartnersAccess} from '../environment.js'
import {setAssertRunning} from '../vendor/dev_server/dev-server-2024.js'

export const NotProvidedStoreFQDNError = new AbortError(
"Couldn't obtain the Shopify FQDN because the store FQDN was not provided.",
Expand Down Expand Up @@ -115,6 +116,8 @@ export async function identityFqdn(): Promise<string> {
const productionFqdn = 'accounts.shopify.com'
switch (environment) {
case 'local':
// force identity uptime check to pass
setAssertRunning((projectName) => projectName === 'identity')
return new DevServer('identity').host()
default:
return productionFqdn
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,3 @@ export function getIpFromHosts(hostname: string) {

throw new Error(`No IP found for hostname: ${hostname}`)
}

// eslint-disable-next-line @typescript-eslint/naming-convention
export function TEST_ClearCache() {
hostToIpCache = {}
lastModifiedTime = 0
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ let checkPort: ReturnType<typeof getCheckPortHelper>

export function assertConnectable(options: ConnectionArguments): void {
checkPort ||= getCheckPortHelper()
// debugger

const {port, addr, timeout = DEFAULT_CONNECT_TIMEOUT} = options
try {
Expand All @@ -33,11 +34,6 @@ export function assertConnectable(options: ConnectionArguments): void {
}
}

// eslint-disable-next-line @typescript-eslint/naming-convention
export function TEST_testResetCheckPort(): void {
checkPort = getCheckPortHelper()
}

function getCheckPortHelper(): (addr: string, port: number, timeout: number) => boolean {
return fallbackCheckPort
}
Expand Down
Loading