Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert to typescript #92

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
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
2 changes: 2 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ jobs:
uses: actions/setup-node@master
with:
node-version: 14.0.0
- name: Compile
run: npm run build
- id: publish
uses: JS-DevTools/npm-publish@v1
with:
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ node_modules/
package-lock.json
.vscode

*-cache.json
*-cache.json
src/**/*.js
test/**/*.js
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"main": "index.js",
"scripts": {
"test": "mocha --recursive --reporter spec --exit",
"pretest": "npm run lint",
"pretest": "npm run lint && npm run build",
"build": "tsc",
"lint": "standard",
"fix": "standard --fix"
},
Expand All @@ -24,11 +25,16 @@
},
"homepage": "https://github.com/PrismarineJS/prismarine-auth#readme",
"devDependencies": {
"@tsconfig/recommended": "^1.0.3",
"@types/mocha": "^10.0.6",
"@types/node": "^20.11.0",
"@types/node-fetch": "^2.6.10",
"chai": "^4.3.4",
"chai-as-promised": "^7.1.1",
"mocha": "^10.0.0",
"prismarine-auth": "file:.",
"standard": "^17.0.0",
"prismarine-auth": "file:."
"typescript": "^5.3.3"
},
"dependencies": {
"@azure/msal-node": "^2.0.2",
Expand Down
47 changes: 35 additions & 12 deletions src/MicrosoftAuthFlow.js → src/MicrosoftAuthFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const XboxTokenManager = require('./TokenManagers/XboxTokenManager')
const MsaTokenManager = require('./TokenManagers/MsaTokenManager')
const BedrockTokenManager = require('./TokenManagers/MinecraftBedrockTokenManager')

async function retry (methodFn, beforeRetry, times) {
async function retry<T> (methodFn: () => Promise<T>, beforeRetry: () => Promise<any> | any, times: number) {
while (times--) {
if (times !== 0) {
try { return await methodFn() } catch (e) { if (e instanceof URIError) { throw e } else { debug(e) } }
Expand All @@ -25,18 +25,37 @@ async function retry (methodFn, beforeRetry, times) {
}
}

type MicrosoftAuthFlowOptions = {
flow: 'live' | 'sisu' | 'msal',
authTitle?: string | undefined,
relyingParty?: string | undefined,
password?: string | undefined,
fetchEntitlements?: boolean | undefined,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

afaik fetch### shouldn't be part of the Authflow options

fetchProfile?: boolean | undefined,
fetchCertificates?: boolean | undefined
}

class MicrosoftAuthFlow {
constructor (username = '', cache = __dirname, options, codeCallback) {
username: string
options: MicrosoftAuthFlowOptions
xbl?: typeof XboxTokenManager | undefined
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

xbl through to doTitleAuth shouldn't be marked as public properties, they're internal and are not part of the API so shouldn't be documented in types.

msa?: typeof MsaTokenManager | undefined
mba?: typeof BedrockTokenManager | undefined
mca?: typeof JavaTokenManager | undefined
doTitleAuth?: boolean | undefined
codeCallback: (response: any) => void
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The response in this callback can vary depending on if MsaTokenManager is being used or LiveTokenManager.

https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/603098e62124b90c13dcd6e57a7d83d95cc07ce8/lib/msal-common/src/response/DeviceCodeResponse.ts#L15

Live - Returns ServerDeviceCodeResponse
Msa - Returns DeviceCodeResponse


constructor (username: string = '', cache: string = __dirname, options: MicrosoftAuthFlowOptions, codeCallback: (response: any) => void) {
this.username = username
if (options && !options.flow) {
throw new Error("Missing 'flow' argument in options. See docs for more information.")
}
this.options = options || { flow: 'msal' }
this.options = options ?? { flow: 'msal' }
this.initTokenManagers(username, cache)
this.codeCallback = codeCallback
}

initTokenManagers (username, cache) {
initTokenManagers (username: string, cache: string | (({ cacheName, username }: { cacheName: string, username: string }) => any)) {
if (typeof cache !== 'function') {
let cachePath = cache

Expand Down Expand Up @@ -74,7 +93,7 @@ class MicrosoftAuthFlow {
this.mca = new JavaTokenManager(cache({ cacheName: 'mca', username }))
}

static resetTokenCaches (cache) {
static resetTokenCaches (cache: string) {
if (!cache) throw new Error('You must provide a cache directory to reset.')
try {
if (fs.existsSync(cache)) {
Expand All @@ -94,7 +113,7 @@ class MicrosoftAuthFlow {
return token
} else {
debug('[msa] No valid cached tokens, need to sign in')
const ret = await this.msa.authDeviceCode((response) => {
const ret = await this.msa.authDeviceCode((response: any) => {
if (this.codeCallback) return this.codeCallback(response)
console.info('[msa] First time signing in. Please authenticate now:')
console.info(response.message)
Expand Down Expand Up @@ -149,8 +168,12 @@ class MicrosoftAuthFlow {
}, () => { this.msa.forceRefresh = true }, 2)
}

async getMinecraftJavaToken (options = {}) {
const response = { token: '', entitlements: {}, profile: {} }
async getMinecraftJavaToken (options: Partial<{
fetchEntitlements: boolean,
fetchProfile: boolean,
fetchCertificates: boolean
}> = {}) {
const response = { token: '', entitlements: {}, profile: {}, certificates: undefined }
if (await this.mca.verifyTokens()) {
debug('[mc] Using existing tokens')
const { token } = await this.mca.getCachedAccessToken()
Expand All @@ -165,19 +188,19 @@ class MicrosoftAuthFlow {
}

if (options.fetchEntitlements) {
response.entitlements = await this.mca.fetchEntitlements(response.token).catch(e => debug('Failed to obtain entitlement data', e))
response.entitlements = await this.mca.fetchEntitlements(response.token).catch((e: Error) => debug('Failed to obtain entitlement data', e))
}
if (options.fetchProfile) {
response.profile = await this.mca.fetchProfile(response.token).catch(e => debug('Failed to obtain profile data', e))
response.profile = await this.mca.fetchProfile(response.token).catch((e: Error) => debug('Failed to obtain profile data', e))
}
if (options.fetchCertificates) {
response.certificates = await this.mca.fetchCertificates(response.token).catch(e => debug('Failed to obtain keypair data', e))
response.certificates = await this.mca.fetchCertificates(response.token).catch((e: Error) => debug('Failed to obtain keypair data', e))
}

return response
}

async getMinecraftBedrockToken (publicKey) {
async getMinecraftBedrockToken (publicKey: string | undefined = undefined) {
// TODO: Fix cache, in order to do cache we also need to cache the ECDH keys so disable it
// is this even a good idea to cache?
if (await this.mba.verifyTokens() && false) { // eslint-disable-line
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import { Response } from "node-fetch"

const debug = require('debug')('prismarine-auth')
const fetch = require('node-fetch')

const { Endpoints } = require('../common/Constants')
const { checkStatus } = require('../common/Util')

const FileCache = require('../common/cache/FileCache')

class LiveTokenManager {
constructor (clientId, scopes, cache) {
clientId: string
scopes: string
cache: typeof FileCache
forceRefresh?: boolean | undefined
polling?: boolean | undefined

constructor (clientId: string, scopes: string, cache: typeof FileCache) {
this.clientId = clientId
this.scopes = scopes
this.cache = cache
Expand Down Expand Up @@ -55,20 +65,20 @@ class LiveTokenManager {
async getAccessToken () {
const { token } = await this.cache.getCached()
if (!token) return
const until = new Date(token.obtainedOn + token.expires_in) - Date.now()
const until = new Date(token.obtainedOn + token.expires_in).getTime() - Date.now()
const valid = until > 1000
return { valid, until, token: token.access_token }
}

async getRefreshToken () {
const { token } = await this.cache.getCached()
if (!token) return
const until = new Date(token.obtainedOn + token.expires_in) - Date.now()
const until = new Date(token.obtainedOn + token.expires_in).getTime() - Date.now()
const valid = until > 1000
return { valid, until, token: token.refresh_token }
}

async updateCache (data) {
async updateCache (data: any) {
await this.cache.setCachedPartial({
token: {
...data,
Expand All @@ -77,7 +87,7 @@ class LiveTokenManager {
})
}

async authDeviceCode (deviceCodeCallback) {
async authDeviceCode (deviceCodeCallback: (resp: Response) => void) {
const acquireTime = Date.now()
const codeRequest = {
method: 'post',
Expand All @@ -90,10 +100,10 @@ class LiveTokenManager {

debug('Requesting live device token', codeRequest)

const cookies = []
const cookies: string[] = []

const res = await fetch(Endpoints.LiveDeviceCodeRequest, codeRequest)
.then(res => {
.then((res: Response) => {
if (res.status !== 200) {
res.text().then(console.warn)
throw Error('Failed to request live.com device code')
Expand All @@ -104,7 +114,7 @@ class LiveTokenManager {
}
return res
})
.then(checkStatus).then(resp => {
.then(checkStatus).then((resp: any) => {
resp.message = `To sign in, use a web browser to open the page ${resp.verification_uri} and use the code ${resp.user_code} or visit http://microsoft.com/link?otc=${resp.user_code}`
deviceCodeCallback(resp)
return resp
Expand All @@ -129,7 +139,7 @@ class LiveTokenManager {
}

const token = await fetch(Endpoints.LiveTokenRequest + '?client_id=' + this.clientId, verifi)
.then(res => res.json()).then(res => {
.then((res: Response) => res.json()).then((res: any) => {
if (res.error) {
if (res.error === 'authorization_pending') {
debug('[live] Still waiting:', res.error_description)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ const fetch = require('node-fetch')
const { Endpoints } = require('../common/Constants')
const { checkStatus } = require('../common/Util')

const FileCache = require('../common/cache/FileCache')

class BedrockTokenManager {
constructor (cache) {
cache: typeof FileCache
forceRefresh?: boolean | undefined

constructor (cache: typeof FileCache) {
this.cache = cache
}

Expand All @@ -15,16 +20,16 @@ class BedrockTokenManager {
if (!token) return
debug('Auth token', token)
const jwt = token.chain[0]
const [header, payload, signature] = jwt.split('.').map(k => Buffer.from(k, 'base64')) // eslint-disable-line
const payload = jwt.split('.').map((k: string) => Buffer.from(k, 'base64'))[1]

const body = JSON.parse(String(payload))
const expires = new Date(body.exp * 1000)
const remainingMs = expires - Date.now()
const remainingMs = expires.getTime() - Date.now()
const valid = remainingMs > 1000
return { valid, until: expires, chain: token.chain }
}

async setCachedAccessToken (data) {
async setCachedAccessToken (data: any) {
await this.cache.setCachedPartial({
mca: {
...data,
Expand All @@ -45,7 +50,7 @@ class BedrockTokenManager {
return false
}

async getAccessToken (clientPublicKey, xsts) {
async getAccessToken (clientPublicKey: string, xsts: any) {
debug('[mc] authing to minecraft', clientPublicKey, xsts)
const headers = {
'Content-Type': 'application/json',
Expand Down
Loading
Loading