authentication and session management
Login, CAPTCHA constants, JWT session, request headers, and the authenticated gate. Crypto details: Encryption and security. Failures: Error handling.
Pieces#
flowchart TB WP[WebPortal.student_login] DC[DEFCAPTCHA] SP[serialize_payload] API[StudentPortalAPI tokens] S[WebPortalSession] H[get_headers] HIT[__hit] WP --> DC WP --> SP WP --> API API --> S S --> H HIT --> H
Two-step login#
async student_login(username, password, captcha = DEFCAPTCHA)
sequenceDiagram
participant App
participant WP as WebPortal
participant Enc as serialize_payload
participant P as StudentPortalAPI
App->>WP: student_login(user, pass)
WP->>Enc: { username, usertype: S, captcha }
WP->>P: POST /token/pretoken-check
P-->>WP: response
WP->>WP: drop rejectedData, Modulename STUDENTMODULE, passwordotpvalue
WP->>Enc: payload2
WP->>P: POST /token/generate-token1
P-->>WP: response.regdata
WP->>WP: this.session = new WebPortalSession
WP-->>App: sessionPhase 1 body:
{ username, usertype: "S", captcha }
captcha defaults to DEFCAPTCHA. Both phases use LoginError as options.exception.
Phase 2 copies resp.response, deletes rejectedData, sets Modulename to "STUDENTMODULE" and passwordotpvalue to the password.
DEFCAPTCHA#
export const DEFCAPTCHA = { captcha: "phw5n", hidden: "gmBctEffdSg=" };
Same trick as pyjiit: a canned captcha pair instead of the image. If the portal rotates the challenge, login starts throwing LoginError.
flowchart LR def[DEFCAPTCHA phw5n] --> pret[/token/pretoken-check] pret --> tok[/token/generate-token1]
Session object#
let expiry_timestamp = JSON.parse(atob(this.token.split(".")[1]))["exp"];
this.expiry = new Date(expiry_timestamp * 1000);
The library stores expiry. __hit does not compare it to Date.now() before sending. Expiry shows up as HTTP 401 → SessionExpired.
async get_headers() {
const localname = await generate_local_name();
return {
Authorization: `Bearer ${this.token}`,
LocalName: localname,
};
}
flowchart LR JWT[token] --> Auth["Authorization Bearer"] GN[generate_local_name] --> LN[LocalName] Auth --> HDR[request headers] LN --> HDR
Unauthenticated __hit (login) still sends LocalName without Bearer.
Gate#
function authenticated(method) {
return function (...args) {
if (this.session == null) {
throw new NotLoggedIn();
}
return method.apply(this, args);
};
}
Applied to the list in API reference. student_login is not wrapped. fill_feedback_form is not wrapped.
There is no logout method. Set portal.session = null if you need to drop credentials in memory.
stateDiagram-v2 [*] --> Empty: new WebPortal Empty --> Live: student_login Live --> Empty: session = null Live --> Expired: HTTP 401 SessionExpired
Header vs body encryption#
| Piece | Encrypted? |
|---|---|
| Login bodies | yes, serialize_payload, sent as body |
LocalName |
yes, generate_local_name |
| JWT | portal-issued, sent in Authorization |
| Many later JSON bodies | serialize_payload then options.json |
| Personal info, bank, password, hostel, fee summary, attendance meta | plain JSON |
__hit never decrypts responses.