Files
Microsoft-Rewards-Script/src/browser/auth/methods/MobileAccessLogin.ts
2026-01-05 16:26:47 +01:00

140 lines
5.4 KiB
TypeScript

import type { Page } from 'patchright'
import { randomBytes } from 'crypto'
import { URLSearchParams } from 'url'
import type { MicrosoftRewardsBot } from '../../../index'
export class MobileAccessLogin {
private clientId = '0000000040170455'
private authUrl = 'https://login.live.com/oauth20_authorize.srf'
private redirectUrl = 'https://login.live.com/oauth20_desktop.srf'
private tokenUrl = 'https://login.microsoftonline.com/consumers/oauth2/v2.0/token'
private scope = 'service::prod.rewardsplatform.microsoft.com::MBI_SSL'
private maxTimeout = 180_000 // 3min
constructor(
private bot: MicrosoftRewardsBot,
private page: Page
) {}
async get(email: string): Promise<string> {
try {
const authorizeUrl = new URL(this.authUrl)
authorizeUrl.searchParams.append('response_type', 'code')
authorizeUrl.searchParams.append('client_id', this.clientId)
authorizeUrl.searchParams.append('redirect_uri', this.redirectUrl)
authorizeUrl.searchParams.append('scope', this.scope)
authorizeUrl.searchParams.append('state', randomBytes(16).toString('hex'))
authorizeUrl.searchParams.append('access_type', 'offline_access')
authorizeUrl.searchParams.append('login_hint', email)
this.bot.logger.debug(
this.bot.isMobile,
'LOGIN-APP',
`Auth URL constructed: ${authorizeUrl.origin}${authorizeUrl.pathname}`
)
await this.bot.browser.utils.disableFido(this.page)
this.bot.logger.debug(this.bot.isMobile, 'LOGIN-APP', 'Navigating to OAuth authorize URL')
await this.page.goto(authorizeUrl.href).catch(err => {
this.bot.logger.debug(
this.bot.isMobile,
'LOGIN-APP',
`page.goto() failed: ${err instanceof Error ? err.message : String(err)}`
)
})
this.bot.logger.info(this.bot.isMobile, 'LOGIN-APP', 'Waiting for mobile OAuth code...')
const start = Date.now()
let code = ''
let lastUrl = ''
while (Date.now() - start < this.maxTimeout) {
const currentUrl = this.page.url()
// Log only when URL changes (high signal, no spam)
if (currentUrl !== lastUrl) {
this.bot.logger.debug(this.bot.isMobile, 'LOGIN-APP', `OAuth poll URL changed → ${currentUrl}`)
lastUrl = currentUrl
}
try {
const url = new URL(currentUrl)
if (url.hostname === 'login.live.com' && url.pathname === '/oauth20_desktop.srf') {
code = url.searchParams.get('code') || ''
if (code) {
this.bot.logger.debug(this.bot.isMobile, 'LOGIN-APP', 'OAuth code detected in redirect URL')
break
}
}
} catch (err) {
this.bot.logger.debug(
this.bot.isMobile,
'LOGIN-APP',
`Invalid URL while polling: ${String(currentUrl)}`
)
}
await this.bot.utils.wait(1000)
}
if (!code) {
this.bot.logger.warn(
this.bot.isMobile,
'LOGIN-APP',
`Timed out waiting for OAuth code after ${Math.round((Date.now() - start) / 1000)}s`
)
this.bot.logger.debug(this.bot.isMobile, 'LOGIN-APP', `Final page URL: ${this.page.url()}`)
return ''
}
const data = new URLSearchParams()
data.append('grant_type', 'authorization_code')
data.append('client_id', this.clientId)
data.append('code', code)
data.append('redirect_uri', this.redirectUrl)
this.bot.logger.debug(this.bot.isMobile, 'LOGIN-APP', 'Exchanging OAuth code for access token')
const response = await this.bot.axios.request({
url: this.tokenUrl,
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
data: data.toString()
})
const token = (response?.data?.access_token as string) ?? ''
if (!token) {
this.bot.logger.warn(this.bot.isMobile, 'LOGIN-APP', 'No access_token in token response')
this.bot.logger.debug(
this.bot.isMobile,
'LOGIN-APP',
`Token response payload: ${JSON.stringify(response?.data)}`
)
return ''
}
this.bot.logger.info(this.bot.isMobile, 'LOGIN-APP', 'Mobile access token received')
return token
} catch (error) {
this.bot.logger.error(
this.bot.isMobile,
'LOGIN-APP',
`MobileAccess error: ${error instanceof Error ? error.stack || error.message : String(error)}`
)
return ''
} finally {
this.bot.logger.debug(this.bot.isMobile, 'LOGIN-APP', 'Returning to base URL')
await this.page.goto(this.bot.config.baseURL, { timeout: 10000 }).catch(() => {})
}
}
}