xlicore
Version:
Currently only used for my own launcher, proper docs will come later (hopefully).
121 lines (120 loc) • 4.06 kB
JavaScript
import ky from 'ky';
import * as DraslAuthenticate from '../types/meta/auth/drasl/authenticate.js';
import path from 'path';
import fs from 'fs';
import fsp from 'fs/promises';
import fswin from 'fswin';
export class DraslAuth {
opts;
authserver;
authFilepath;
constructor(opts) {
this.opts = opts;
this.authserver = opts.server;
if (opts.saveDir)
this.authFilepath = path.resolve(opts.saveDir, '.super_secret.json');
}
async init(creds) {
let json;
if (this.authFilepath && fs.existsSync(this.authFilepath)) {
json = await fsp.readFile(this.authFilepath, { encoding: 'utf8' }).then(async (val) => {
const json = JSON.parse(val);
if (!DraslAuthenticate.isValidResponse(json) || !(await this.validate(json))) {
return await this.refresh(json);
}
return json;
});
}
else {
json = await this.first(creds);
}
return {
accessToken: json.accessToken,
clientId: json.clientToken,
userType: 'mojang',
uuid: json.selectedProfile.id,
name: json.selectedProfile.name,
drasl: {
server: this.opts.server
}
};
}
async first(creds) {
if (!creds.password)
throw new Error('[Drasl Auth] Password not specified!');
const body = {
agent: {
name: 'Minecraft',
version: 1
},
username: creds.username,
password: creds.password,
requestUser: true
};
const resp = await ky
.post(this.authserver + '/authenticate', {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
})
.json();
await this.writeJson(resp);
return resp;
}
async refresh(body) {
const resp = await ky
.post(this.authserver + '/refresh', {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
})
.json();
await this.writeJson(resp);
return resp;
}
async invalidate(body) {
await ky.post(this.authserver + '/refresh', {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
}
async writeJson(json) {
if (!this.authFilepath)
return;
fswin.setAttributesSync(this.authFilepath, { IS_HIDDEN: false });
return await fsp.writeFile(this.authFilepath, JSON.stringify(json), { encoding: 'utf8' }).then(() => {
fswin.setAttributesSync(this.authFilepath, { IS_HIDDEN: true }); // FIXME: if porting to other OSes
});
}
async validate(json) {
const body = {
accessToken: json.accessToken,
clientToken: json.clientToken
};
const resp = await ky.post(this.authserver + '/validate', {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (resp.status == 204)
return true;
if (resp.status == 403)
return false;
throw new Error(`Unhandled status ${resp.status}: ${resp.statusText}!`);
}
async signout(creds) {
if (!creds.password)
throw new Error(`No password! Can't sign out.`);
const body = {
username: creds.username,
password: creds.password
};
const resp = await ky.post(this.authserver + 'signout', {
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
});
if (resp.status == 401)
throw new Error(`Invalid credentials! Can't sign out.`);
if (resp.status != 204)
throw new Error(`Unhandled status ${resp.status}: ${resp.statusText}!`);
}
}