Some checks failed
Axolotl desktop CI / guardrails (push) Has been cancelled
Axolotl desktop CI / desktop (macos-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (ubuntu-latest) (push) Has been cancelled
Axolotl desktop CI / desktop (windows-latest) (push) Has been cancelled
Axolotl desktop CI / website (push) Has been cancelled
Repository checks / typos (push) Has been cancelled
Repository checks / tombi (push) Has been cancelled
Rust checks / shear (push) Has been cancelled
Sync source to CNB / Sync Git ref (push) Has been cancelled
Add pack sync markers, tagged mod updates, parallel progress, JWT downloads and retry recovery. Include pending onboarding, about scene, compatibility data pack and download fixes.
384 lines
12 KiB
JavaScript
384 lines
12 KiB
JavaScript
const assert = require('node:assert/strict')
|
|
const fs = require('node:fs')
|
|
const vm = require('node:vm')
|
|
const test = require('node:test')
|
|
const source = fs.readFileSync(require('node:path').join(__dirname, 'skin_site_bridge.js'), 'utf8')
|
|
|
|
function harness(origin = 'https://skin.starlight.cool', skinSize = 64) {
|
|
let token = null
|
|
const listeners = {},
|
|
messages = [],
|
|
requests = [],
|
|
drawCalls = []
|
|
const skinWidth = typeof skinSize === 'number' ? skinSize : skinSize.width
|
|
const skinHeight = typeof skinSize === 'number' ? skinSize : skinSize.height
|
|
let tick
|
|
let result = async () => ({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({ payload: { uuid: 'one', username: 'One' } }),
|
|
})
|
|
const parent = {
|
|
postMessage(data, target) {
|
|
messages.push({ data, target })
|
|
},
|
|
}
|
|
const context = {
|
|
location: { origin },
|
|
window: {
|
|
parent,
|
|
addEventListener(type, callback) {
|
|
listeners[type] = callback
|
|
},
|
|
},
|
|
localStorage: {
|
|
getItem() {
|
|
return token
|
|
},
|
|
},
|
|
fetch(...args) {
|
|
requests.push(args)
|
|
return result(...args)
|
|
},
|
|
AbortController,
|
|
Blob,
|
|
Date,
|
|
FormData,
|
|
Uint8Array,
|
|
atob,
|
|
document: {
|
|
createElement() {
|
|
return {
|
|
getContext() {
|
|
return {
|
|
drawImage(...args) {
|
|
drawCalls.push(args)
|
|
},
|
|
}
|
|
},
|
|
toDataURL() {
|
|
return 'data:image/png;base64,SEVBRERBVEE='
|
|
},
|
|
}
|
|
},
|
|
},
|
|
Image: class {
|
|
naturalWidth = skinWidth
|
|
naturalHeight = skinHeight
|
|
set src(_value) {
|
|
queueMicrotask(() => this.onload?.())
|
|
}
|
|
},
|
|
setTimeout,
|
|
clearTimeout,
|
|
setInterval(callback) {
|
|
tick = callback
|
|
return 1
|
|
},
|
|
clearInterval() {},
|
|
}
|
|
vm.runInNewContext(source, context)
|
|
return {
|
|
drawCalls,
|
|
messages,
|
|
requests,
|
|
listeners,
|
|
parent,
|
|
token(value) {
|
|
token = value
|
|
},
|
|
result(value) {
|
|
result = value
|
|
},
|
|
async tick() {
|
|
tick?.()
|
|
await new Promise(setImmediate)
|
|
},
|
|
async connect(origin = 'http://localhost:5201', source = parent) {
|
|
listeners.message?.({ source, origin, data: { type: 'starlight-skin-session-connect' } })
|
|
await new Promise(setImmediate)
|
|
},
|
|
async message(data, origin = 'http://localhost:5201', source = parent) {
|
|
listeners.message?.({ source, origin, data })
|
|
await new Promise(setImmediate)
|
|
},
|
|
async waitForMessage(type, requestId, timeout = 2_000) {
|
|
const deadline = Date.now() + timeout
|
|
while (Date.now() < deadline) {
|
|
const message = messages.find(
|
|
(entry) =>
|
|
entry.data?.type === type &&
|
|
(requestId === undefined || entry.data?.requestId === requestId),
|
|
)
|
|
if (message) return message
|
|
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
}
|
|
throw new Error(`Timed out waiting for ${type}`)
|
|
},
|
|
}
|
|
}
|
|
|
|
test('bridge is restricted to the skin origin and an allowlisted parent', async () => {
|
|
assert.equal(harness('https://evil.example').listeners.message, undefined)
|
|
const h = harness()
|
|
h.token('test-token')
|
|
await h.connect('https://evil.example')
|
|
await h.connect('http://localhost:5201', {})
|
|
assert.equal(h.requests.length, 0)
|
|
assert.equal(h.messages.length, 0)
|
|
await h.connect()
|
|
assert.equal(h.requests[0][0], '/starlight/user')
|
|
assert.equal(h.requests[0][1].headers.Authorization, 'Bearer test-token')
|
|
assert.equal(h.requests[0][1].redirect, 'error')
|
|
assert.equal(h.messages.at(-1).data.status, 'signed-in')
|
|
assert.ok(h.messages.every((m) => m.target === 'http://localhost:5201'))
|
|
assert.ok(!JSON.stringify(h.messages).includes('test-token'))
|
|
})
|
|
|
|
test('logout, invalid token, network error and account switching replace old state', async () => {
|
|
const h = harness()
|
|
await h.connect()
|
|
assert.equal(h.messages.at(-1).data.status, 'signed-out')
|
|
h.token('first')
|
|
await h.tick()
|
|
assert.equal(h.messages.at(-1).data.user.username, 'One')
|
|
h.result(async () => ({ ok: false, status: 401 }))
|
|
h.token('expired')
|
|
await h.tick()
|
|
assert.equal(h.messages.at(-1).data.status, 'signed-out')
|
|
h.result(async () => {
|
|
throw Error('offline')
|
|
})
|
|
h.token('network-error')
|
|
await h.tick()
|
|
assert.equal(h.messages.at(-1).data.status, 'error')
|
|
h.token(null)
|
|
await h.tick()
|
|
assert.equal(h.messages.at(-1).data.status, 'signed-out')
|
|
})
|
|
|
|
test('pack JWT is returned only for an explicit request from the connected launcher', async () => {
|
|
const h = harness()
|
|
h.token('pack.jwt.secret')
|
|
const request = { type: 'starlight-pack-token-request', requestId: 'pack-token-1-1' }
|
|
await h.message(request)
|
|
await h.connect('https://evil.example')
|
|
assert.equal(h.messages.length, 0)
|
|
await h.connect()
|
|
assert.ok(!JSON.stringify(h.messages).includes('pack.jwt.secret'))
|
|
await h.message(request, 'https://evil.example')
|
|
await h.message(request, 'http://localhost:5201', {})
|
|
assert.ok(!JSON.stringify(h.messages).includes('pack.jwt.secret'))
|
|
await h.message(request)
|
|
assert.equal(h.messages.at(-1).data.type, 'starlight-pack-token-result')
|
|
assert.equal(h.messages.at(-1).data.token, 'pack.jwt.secret')
|
|
assert.equal(h.messages.at(-1).target, 'http://localhost:5201')
|
|
h.token('unverified.account.token')
|
|
await h.message({ ...request, requestId: 'pack-token-1-2' })
|
|
assert.equal(h.messages.at(-1).data.token, null)
|
|
h.token(null)
|
|
await h.message({ ...request, requestId: 'pack-token-1-3' })
|
|
assert.equal(h.messages.at(-1).data.token, null)
|
|
})
|
|
|
|
test('a delayed response cannot restore the user after logout', async () => {
|
|
const h = harness()
|
|
let finish
|
|
h.result(
|
|
() =>
|
|
new Promise((resolve) => {
|
|
finish = resolve
|
|
}),
|
|
)
|
|
h.token('first')
|
|
await h.connect()
|
|
h.token(null)
|
|
await h.tick()
|
|
finish({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({ payload: { uuid: 'one', username: 'One' } }),
|
|
})
|
|
await new Promise(setImmediate)
|
|
assert.equal(h.messages.at(-1).data.status, 'signed-out')
|
|
assert.ok(!h.messages.some((m) => m.data.status === 'signed-in'))
|
|
})
|
|
|
|
test('luck requests stay on the skin origin and return only the validated score', async () => {
|
|
const h = harness()
|
|
h.token('test-token')
|
|
await h.connect()
|
|
h.result(async () => ({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({ payload: { luck: 73 } }),
|
|
}))
|
|
await h.message({ type: 'starlight-skin-luck-request', requestId: 'skin-luck-1-1' })
|
|
const request = h.requests.at(-1)
|
|
assert.equal(request[0], '/starlight/luck')
|
|
assert.equal(request[1].headers.Authorization, 'Bearer test-token')
|
|
assert.equal(request[1].redirect, 'error')
|
|
const message = h.messages.at(-1)
|
|
assert.equal(message.target, 'http://localhost:5201')
|
|
assert.equal(message.data.type, 'starlight-skin-luck-result')
|
|
assert.equal(message.data.requestId, 'skin-luck-1-1')
|
|
assert.equal(message.data.ok, true)
|
|
assert.equal(message.data.luck, 73)
|
|
assert.ok(!JSON.stringify(h.messages).includes('test-token'))
|
|
})
|
|
|
|
test('luck requests reject untrusted parents, missing sessions, and invalid scores', async () => {
|
|
const h = harness()
|
|
await h.connect()
|
|
const before = h.requests.length
|
|
await h.message(
|
|
{ type: 'starlight-skin-luck-request', requestId: 'skin-luck-2-1' },
|
|
'https://evil.example',
|
|
)
|
|
assert.equal(h.requests.length, before)
|
|
await h.message({ type: 'starlight-skin-luck-request', requestId: 'skin-luck-2-2' })
|
|
assert.equal(h.requests.length, before)
|
|
assert.equal(h.messages.at(-1).data.ok, false)
|
|
|
|
h.token('test-token')
|
|
h.result(async () => ({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({ payload: { luck: 101 } }),
|
|
}))
|
|
await h.message({ type: 'starlight-skin-luck-request', requestId: 'skin-luck-2-3' })
|
|
assert.equal(h.messages.at(-1).data.ok, false)
|
|
})
|
|
|
|
test('player requests return a sanitized complete player collection without exposing the token', async () => {
|
|
const h = harness()
|
|
h.token('test-token')
|
|
await h.connect()
|
|
h.result(async (url) => {
|
|
if (url.startsWith('/starlight/skin/player/skin/')) {
|
|
return {
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({
|
|
payload: {
|
|
skin: url.endsWith('0123456789abcdef0123456789abcdef')
|
|
? '/textures/player-one.png'
|
|
: null,
|
|
},
|
|
}),
|
|
}
|
|
}
|
|
return {
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({
|
|
payload: [
|
|
{ uuid: '0123456789abcdef0123456789abcdef', name: 'PlayerOne', isMojang: false },
|
|
{ uuid: 'fedcba9876543210fedcba9876543210', name: 'Official', isMojang: true },
|
|
{ uuid: 'bad', name: 'Ignored', isMojang: false },
|
|
],
|
|
}),
|
|
}
|
|
})
|
|
await h.message({ type: 'starlight-skin-players-request', requestId: 'skin-players-1-1' })
|
|
await h.waitForMessage('starlight-skin-players-result', 'skin-players-1-1')
|
|
const request = h.requests.find(([url]) => url === '/starlight/skin/player')
|
|
assert.equal(request[0], '/starlight/skin/player')
|
|
assert.equal(request[1].headers.Authorization, 'Bearer test-token')
|
|
assert.equal(
|
|
h.requests.filter(([url]) => url.startsWith('/starlight/skin/player/skin/')).length,
|
|
2,
|
|
)
|
|
const message = h.messages.at(-1)
|
|
assert.equal(message.data.type, 'starlight-skin-players-result')
|
|
assert.equal(message.data.ok, true)
|
|
assert.equal(message.data.players.length, 2)
|
|
assert.equal(message.data.players[1].isMojang, true)
|
|
assert.equal(message.data.players[0].skinState, 'ready')
|
|
assert.equal(message.data.players[0].headDataUrl, 'data:image/png;base64,SEVBRERBVEE=')
|
|
assert.equal(message.data.players[0].skinDataUrl, 'data:image/png;base64,SEVBRERBVEE=')
|
|
assert.equal(message.data.players[0].model, 'default')
|
|
assert.equal(message.data.players[1].skinState, 'empty')
|
|
assert.equal(message.data.players[1].headDataUrl, undefined)
|
|
assert.ok(!JSON.stringify(h.messages).includes('test-token'))
|
|
})
|
|
|
|
test('skin updates upload a PNG to the selected skin-site player without exposing the token', async () => {
|
|
const h = harness()
|
|
h.token('test-token')
|
|
await h.connect()
|
|
h.result(async (url) => ({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({
|
|
payload:
|
|
url === '/starlight/skin/player'
|
|
? [
|
|
{
|
|
uuid: '0123456789abcdef0123456789abcdef',
|
|
name: 'PlayerOne',
|
|
isMojang: false,
|
|
},
|
|
]
|
|
: { skin: null },
|
|
}),
|
|
}))
|
|
await h.message({ type: 'starlight-skin-players-request', requestId: 'skin-players-2-1' })
|
|
h.result(async () => ({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({ success: true, payload: 'updated' }),
|
|
}))
|
|
await h.message({
|
|
type: 'starlight-skin-update-request',
|
|
requestId: 'skin-update-1-1',
|
|
playerId: '0123456789abcdef0123456789abcdef',
|
|
textureDataUrl: 'data:image/png;base64,SEVBRERBVEE=',
|
|
model: 'slim',
|
|
})
|
|
|
|
const request = h.requests.at(-1)
|
|
assert.equal(
|
|
request[0],
|
|
'/starlight/skin/player/skin/0123456789abcdef0123456789abcdef/SKIN',
|
|
)
|
|
assert.equal(request[1].method, 'PUT')
|
|
assert.equal(request[1].headers.Authorization, 'Bearer test-token')
|
|
assert.equal(request[1].body.get('model'), 'slim')
|
|
assert.equal(request[1].body.get('file').type, 'image/png')
|
|
assert.equal(h.messages.at(-1).data.type, 'starlight-skin-update-result')
|
|
assert.equal(h.messages.at(-1).data.ok, true)
|
|
assert.ok(!JSON.stringify(h.messages).includes('test-token'))
|
|
})
|
|
|
|
test('skin rendering uses the skin site crop and preserves the source texture dimensions', async () => {
|
|
const h = harness('https://skin.starlight.cool', { width: 64, height: 128 })
|
|
h.token('test-token')
|
|
await h.connect()
|
|
h.result(async (url) => ({
|
|
ok: true,
|
|
status: 200,
|
|
json: async () => ({
|
|
payload:
|
|
url === '/starlight/skin/player'
|
|
? [
|
|
{
|
|
uuid: '0123456789abcdef0123456789abcdef',
|
|
name: 'HighResolutionPlayer',
|
|
isMojang: false,
|
|
},
|
|
]
|
|
: { skin: '/textures/hd.png', model: 'slim' },
|
|
}),
|
|
}))
|
|
await h.message({ type: 'starlight-skin-players-request', requestId: 'skin-players-3-1' })
|
|
|
|
const player = h.messages.at(-1).data.players[0]
|
|
assert.equal(player.skinState, 'ready')
|
|
assert.equal(player.headDataUrl, 'data:image/png;base64,SEVBRERBVEE=')
|
|
assert.equal(player.skinDataUrl, 'data:image/png;base64,SEVBRERBVEE=')
|
|
assert.equal(player.model, 'slim')
|
|
assert.deepEqual(h.drawCalls[0].slice(1), [8, 8, 8, 8, 1, 1, 16, 16])
|
|
assert.deepEqual(h.drawCalls[1].slice(1), [40, 8, 8, 8, 0, 0, 18, 18])
|
|
})
|