Skip to content
Open
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: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,17 @@ import { Session } from "ecoledirecte.js";
// Create a new Session.
const session = new Session("identifiant", "motdepasse");

// If you need A2F

// A2F objet
const A2FParams = {
"question": "answer",
"What is your day of birth ?": "25"
// ...
};

// Bring your session to life!
const account = await session.login().catch(err => {
const account = await session.login(/* A2FParams */).catch(err => {
console.error("This login did not go well.");
});

Expand Down
36 changes: 31 additions & 5 deletions lib/Session.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { loginRes, isFailure } from "ecoledirecte-api-types/v3";
import { getMainAccount, login } from "./util";
import { A2FReplies, decodeBase64, encodeBase64, getA2FQCM, getMainAccount, login, postA2FRes } from "./util";
import { Family, Staff, Student, Teacher } from "./accounts";
import { EcoleDirecteAPIError } from "./errors";
import logs from "./events";
Expand Down Expand Up @@ -32,14 +32,40 @@ export class Session {
}

async login(
A2FResponses: A2FReplies | undefined,
context: Record<string, unknown> = {}
): Promise<Family | Staff | Student | Teacher> {
const { _username: username, _password: password } = this;
const loginRes = await login(username, password, context);
let loginRes = await login(username, password, null, context);

this.loginRes = loginRes;
this.token = loginRes.token;
if (isFailure(loginRes)) throw new EcoleDirecteAPIError(loginRes);
if (loginRes.code == 250) {

const A2FQCM = await getA2FQCM(loginRes.token, context);

if (isFailure(A2FQCM)) throw new EcoleDirecteAPIError(A2FQCM);

const question = decodeBase64(A2FQCM.data.question);

if (!A2FResponses) throw new Error("A2F needed");
if (typeof A2FResponses[question] != 'string') throw new Error(`A2F needed for the question : ${question}`);

const A2Ffa = await postA2FRes(loginRes.token, encodeBase64(A2FResponses[question]), context);

if (isFailure(A2Ffa)) throw new EcoleDirecteAPIError(A2Ffa);

const finalLogin = await login(username, password, [ A2Ffa.data ], context);

if (isFailure(finalLogin) || finalLogin.code != 200) throw new EcoleDirecteAPIError(finalLogin);

loginRes = finalLogin
this.token = finalLogin.token;
this.loginRes = finalLogin;

} else {
this.loginRes = loginRes;
this.token = loginRes.token;
if (isFailure(loginRes)) throw new EcoleDirecteAPIError(loginRes);
}

// Login succeeded

Expand Down
40 changes: 37 additions & 3 deletions lib/util/login.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { Routes } from "ecoledirecte-api-types/v3";

import { makeRequest } from "./util";
import { loginRes, account } from "ecoledirecte-api-types/v3";
import { loginRes, account, A2FQCM, A2FQCMRes, Routes, base64 } from "ecoledirecte-api-types/v3";

/**
* @returns EcoleDirecte `/v3/login.awp` response
*/
export async function login(
username: string,
password: string,
fa: Array<{ cv: string, cn:string }> | null,
context: Record<string, unknown> = {}
): Promise<loginRes> {
const body: loginRes = await makeRequest(
Expand All @@ -18,6 +17,7 @@ export async function login(
body: {
identifiant: username,
motdepasse: password,
fa: Array.isArray(fa) ? fa : [],
},
},
{ action: "login", username, password, ...context }
Expand All @@ -26,6 +26,40 @@ export async function login(
return body;
}

/**
* @warning Token is the token sended by the first login response
* @returns EcoleDirecte `/v3/connexion/doubleauth.awp?verbe=get` response
*/
export async function getA2FQCM(token: string, context: Record<string, unknown> = {}): Promise<A2FQCM> {

const body: A2FQCM = await makeRequest({
method: "POST",
path: Routes.getA2FQCM(),
body: {}
}, { action: "A2FQCM", ...context }, undefined, token);

return body;
}

/**
* @warning Token is the token sended by the first login response
* @returns EcoleDirecte `/v3/connexion/doubleauth.awp?verbe=post` response
*/

export async function postA2FRes(token: string, response: base64, context: Record<string, unknown> = {}): Promise<A2FQCMRes> {

const body: A2FQCMRes = await makeRequest({
method: "POST",
path: Routes.postA2FRes(),
body: {
choix: response,
}
}, { action: "A2FQCMRes", ...context }, undefined, token);

return body;
}


/**
* @returns The main account of the array
*/
Expand Down
14 changes: 13 additions & 1 deletion lib/util/util.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
import fetch, { RequestInit } from "node-fetch";
import mime from "mime-types";
import { failureRes, isFailure, root, rootp } from "ecoledirecte-api-types/v3";
import { base64, failureRes, isFailure, root, rootp } from "ecoledirecte-api-types/v3";

import logs from "../events";
import { EcoleDirecteAPIError } from "../errors";
import EventEmitter from "events";
import { Account } from "../accounts";

export type A2FReplies = { [utf8_sentence: string]: string }

export function decodeBase64(str: base64) {
const binaryStrBuffer = Buffer.from(str, 'base64');
return binaryStrBuffer.toString('utf8');
}

export function encodeBase64(str: string) {
const utf8Buffer = Buffer.from(str, 'utf-8');
return utf8Buffer.toString('base64');
}

export function toISODate(date: Date | string | number): string {
const d = new Date(date);
if (isNaN(d.getTime())) throw new Error("Invalid date");
Expand Down
Loading