reddy-api-srm 1.0.10 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -7,6 +7,8 @@ export interface TerminateSessionsInput {
|
|
|
7
7
|
digest: string;
|
|
8
8
|
/** Optional CSRF token (value of iamcsr cookie, without the prefix) */
|
|
9
9
|
csrfToken?: string;
|
|
10
|
+
/** Optional cookie string from validatePassword pre-announcement flow */
|
|
11
|
+
sessionCookie?: string;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
export interface TerminateSessionsResult {
|
|
@@ -27,7 +27,11 @@ exports.terminateSessions = terminateSessions;
|
|
|
27
27
|
* @param {string} digest - Zoho digest from verifyUser
|
|
28
28
|
* @param {string} [csrfToken] - Optional x-zcsrf-token value (extracted from iamcsr cookie)
|
|
29
29
|
*/
|
|
30
|
-
async function terminateSessions({ flowId, identifier, digest, csrfToken }) {
|
|
30
|
+
async function terminateSessions({ flowId, identifier, digest, csrfToken, sessionCookie }) {
|
|
31
|
+
const normalizedCsrfToken = csrfToken ??
|
|
32
|
+
(((sessionCookie ?? "").match(/(?:^|;\s*)iamcsr=([^;]+)/) ??
|
|
33
|
+
(sessionCookie ?? "").match(/(?:^|;\s*)_zcsr_tmp=([^;]+)/) ??
|
|
34
|
+
[])[1] ?? null);
|
|
31
35
|
// ── Strategy 1: Block-Sessions preannouncement POST ────────────────────────
|
|
32
36
|
// This is what fires when the user clicks "Terminate All Sessions" on the
|
|
33
37
|
// SRM concurrent-sessions warning page during a fresh login attempt.
|
|
@@ -58,9 +62,10 @@ async function terminateSessions({ flowId, identifier, digest, csrfToken }) {
|
|
|
58
62
|
"sec-fetch-dest": "empty",
|
|
59
63
|
"sec-fetch-mode": "cors",
|
|
60
64
|
"sec-fetch-site": "same-origin",
|
|
61
|
-
...(
|
|
62
|
-
? { "x-zcsrf-token": `iamcsrcoo=${
|
|
65
|
+
...(normalizedCsrfToken
|
|
66
|
+
? { "x-zcsrf-token": `iamcsrcoo=${normalizedCsrfToken}` }
|
|
63
67
|
: {}),
|
|
68
|
+
...(sessionCookie ? { cookie: sessionCookie } : {}),
|
|
64
69
|
Referer:
|
|
65
70
|
"https://academia.srmist.edu.in/accounts/p/40-10002227248/signin?hide_fp=true&servicename=ZohoCreator&service_language=en&dcc=true&serviceurl=https%3A%2F%2Facademia.srmist.edu.in%2Fportal%2Facademia-academic-services%2FredirectFromLogin",
|
|
66
71
|
"Referrer-Policy": "strict-origin-when-cross-origin",
|
|
@@ -92,9 +97,10 @@ async function terminateSessions({ flowId, identifier, digest, csrfToken }) {
|
|
|
92
97
|
accept: "application/json, text/javascript, */*; q=0.01",
|
|
93
98
|
"accept-language": "en-US,en;q=0.9",
|
|
94
99
|
"x-requested-with": "XMLHttpRequest",
|
|
95
|
-
...(
|
|
96
|
-
? { "x-zcsrf-token": `iamcsrcoo=${
|
|
100
|
+
...(normalizedCsrfToken
|
|
101
|
+
? { "x-zcsrf-token": `iamcsrcoo=${normalizedCsrfToken}` }
|
|
97
102
|
: {}),
|
|
103
|
+
...(sessionCookie ? { cookie: sessionCookie } : {}),
|
|
98
104
|
Referer: "https://accounts.zoho.in/",
|
|
99
105
|
"Referrer-Policy": "strict-origin-when-cross-origin",
|
|
100
106
|
},
|
|
@@ -111,6 +117,41 @@ async function terminateSessions({ flowId, identifier, digest, csrfToken }) {
|
|
|
111
117
|
console.warn("[terminateSessions] Strategy 2 error:", e2);
|
|
112
118
|
}
|
|
113
119
|
}
|
|
120
|
+
// ── Strategy 3: Announcement pre-blocksessions DELETE ───────────────────────
|
|
121
|
+
// This is what current SRM preannouncement page JS calls.
|
|
122
|
+
if (sessionCookie) {
|
|
123
|
+
try {
|
|
124
|
+
const preBlockUrl = "https://academia.srmist.edu.in/accounts/p/40-10002227248/webclient/v1/announcement/pre/blocksessions";
|
|
125
|
+
const res3 = await fetch(preBlockUrl, {
|
|
126
|
+
method: "DELETE",
|
|
127
|
+
headers: {
|
|
128
|
+
accept: "application/json, text/javascript, */*; q=0.01",
|
|
129
|
+
"accept-language": "en-US,en;q=0.9",
|
|
130
|
+
"content-type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
131
|
+
...(normalizedCsrfToken
|
|
132
|
+
? {
|
|
133
|
+
"x-zcsrf-token": `iamcsrcoo=${encodeURIComponent(normalizedCsrfToken)}`,
|
|
134
|
+
"X-ZCSRF-TOKEN": `iamcsrcoo=${encodeURIComponent(normalizedCsrfToken)}`,
|
|
135
|
+
}
|
|
136
|
+
: {}),
|
|
137
|
+
cookie: sessionCookie,
|
|
138
|
+
Referer: "https://academia.srmist.edu.in/accounts/p/40-10002227248/preannouncement/block-sessions",
|
|
139
|
+
"Referrer-Policy": "strict-origin-when-cross-origin",
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
let data = null;
|
|
143
|
+
try {
|
|
144
|
+
data = await res3.json();
|
|
145
|
+
} catch (_) { }
|
|
146
|
+
if (res3.ok || res3.status === 200 || res3.status === 202 || res3.status === 204 ||
|
|
147
|
+
(data && (String(data.status_code) === "204" || data.code === "SUCCESS"))) {
|
|
148
|
+
return { success: true, status: res3.status, strategy: "announcement-pre-blocksessions", data };
|
|
149
|
+
}
|
|
150
|
+
console.warn("[terminateSessions] Strategy 3 (announcement pre-blocksessions DELETE) returned:", res3.status, data);
|
|
151
|
+
} catch (e3) {
|
|
152
|
+
console.warn("[terminateSessions] Strategy 3 error:", e3);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
114
155
|
|
|
115
156
|
// Both strategies failed — return a non-fatal "best-effort" result.
|
|
116
157
|
// The caller (handleTerminateAndRetry) will still attempt to re-login because
|
|
@@ -1,6 +1,51 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.validatePassword = validatePassword;
|
|
4
|
+
function parseCookiePairsFromSetCookie(headers) {
|
|
5
|
+
const pairs = [];
|
|
6
|
+
for (const header of headers) {
|
|
7
|
+
const pair = header.split(";")[0]?.trim();
|
|
8
|
+
if (pair && pair.includes("="))
|
|
9
|
+
pairs.push(pair);
|
|
10
|
+
}
|
|
11
|
+
return pairs;
|
|
12
|
+
}
|
|
13
|
+
function parseCookiePairsFromCookieString(cookie) {
|
|
14
|
+
if (!cookie)
|
|
15
|
+
return [];
|
|
16
|
+
return cookie
|
|
17
|
+
.split(";")
|
|
18
|
+
.map((v) => v.trim())
|
|
19
|
+
.filter((v) => v.includes("="));
|
|
20
|
+
}
|
|
21
|
+
function mergeCookies(...cookieSources) {
|
|
22
|
+
const cookieMap = new Map();
|
|
23
|
+
for (const source of cookieSources) {
|
|
24
|
+
for (const pair of parseCookiePairsFromCookieString(source)) {
|
|
25
|
+
const idx = pair.indexOf("=");
|
|
26
|
+
if (idx <= 0)
|
|
27
|
+
continue;
|
|
28
|
+
const name = pair.slice(0, idx).trim();
|
|
29
|
+
cookieMap.set(name, pair.trim());
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return `${Array.from(cookieMap.values()).join("; ")};`;
|
|
33
|
+
}
|
|
34
|
+
function getCookieValue(cookie, name) {
|
|
35
|
+
const match = (cookie || "").match(new RegExp(`(?:^|;\\s*)${name}=([^;]+)`));
|
|
36
|
+
return match ? match[1] : null;
|
|
37
|
+
}
|
|
38
|
+
function getFlowIdFromUrl(urlValue) {
|
|
39
|
+
if (!urlValue)
|
|
40
|
+
return null;
|
|
41
|
+
try {
|
|
42
|
+
const u = new URL(urlValue, "https://academia.srmist.edu.in");
|
|
43
|
+
return u.searchParams.get("flowId") ?? u.searchParams.get("flow_id") ?? null;
|
|
44
|
+
}
|
|
45
|
+
catch (_) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
4
49
|
async function getSigninSessionHeaders() {
|
|
5
50
|
const signinUrl = "https://academia.srmist.edu.in/accounts/p/40-10002227248/signin?hide_fp=true&servicename=ZohoCreator&service_language=en&dcc=true&serviceurl=https%3A%2F%2Facademia.srmist.edu.in%2Fportal%2Facademia-academic-services%2FredirectFromLogin";
|
|
6
51
|
const seedRes = await fetch(signinUrl, {
|
|
@@ -80,17 +125,54 @@ async function validatePassword({ identifier, digest, password, }) {
|
|
|
80
125
|
}
|
|
81
126
|
return { error: "Internal Server Error", errorReason: new Error("Non-JSON response from login endpoint") };
|
|
82
127
|
}
|
|
128
|
+
const responseSetCookieHeaders = (typeof res.headers.getSetCookie === "function"
|
|
129
|
+
? res.headers.getSetCookie()
|
|
130
|
+
: [res.headers.get("set-cookie")].filter(Boolean));
|
|
131
|
+
const responseCookies = parseCookiePairsFromSetCookie(responseSetCookieHeaders).join("; ");
|
|
132
|
+
const sessionCookie = mergeCookies(seed.cookie, responseCookies);
|
|
133
|
+
const sessionCsrfToken = getCookieValue(sessionCookie, "iamcsr") ?? getCookieValue(sessionCookie, "_zcsr_tmp");
|
|
134
|
+
const preAnnouncementRedirect = response?.passwordauth?.redirect_uri ?? null;
|
|
135
|
+
const isPreAnnouncementFlow = response?.code === "SI303" ||
|
|
136
|
+
(typeof response?.message === "string" &&
|
|
137
|
+
response.message.toLowerCase().includes("pre announcement")) ||
|
|
138
|
+
(typeof preAnnouncementRedirect === "string" &&
|
|
139
|
+
preAnnouncementRedirect.includes("/preannouncement/block-sessions"));
|
|
140
|
+
if (isPreAnnouncementFlow) {
|
|
141
|
+
return {
|
|
142
|
+
data: {
|
|
143
|
+
statusCode: 435,
|
|
144
|
+
message: "Maximum concurrent sessions reached. Please terminate existing sessions to continue.",
|
|
145
|
+
captcha: { required: false, digest: null },
|
|
146
|
+
isConcurrentLimit: true,
|
|
147
|
+
flowId: getFlowIdFromUrl(preAnnouncementRedirect),
|
|
148
|
+
sessionCookie,
|
|
149
|
+
csrfToken: sessionCsrfToken,
|
|
150
|
+
},
|
|
151
|
+
isAuthenticated: false,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
83
154
|
if (response.status_code === 201 || response.status_code === 200) {
|
|
84
|
-
|
|
85
|
-
? res.headers.getSetCookie()
|
|
86
|
-
: [res.headers.get("set-cookie")].filter(Boolean));
|
|
87
|
-
if (!setCookieHeaders.length)
|
|
155
|
+
if (!responseSetCookieHeaders.length)
|
|
88
156
|
throw new Error("Couldn't able to get cookie from response header ");
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
157
|
+
let extractedCookies = sessionCookie;
|
|
158
|
+
// Complete the service redirect once to receive portal/session cookies needed by data APIs.
|
|
159
|
+
try {
|
|
160
|
+
const bridgeRes = await fetch("https://academia.srmist.edu.in/portal/academia-academic-services/redirectFromLogin", {
|
|
161
|
+
method: "GET",
|
|
162
|
+
headers: {
|
|
163
|
+
accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
164
|
+
"accept-language": "en-US,en;q=0.9",
|
|
165
|
+
cookie: extractedCookies,
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
const bridgeSetCookies = (typeof bridgeRes.headers.getSetCookie === "function"
|
|
169
|
+
? bridgeRes.headers.getSetCookie()
|
|
170
|
+
: [bridgeRes.headers.get("set-cookie")].filter(Boolean));
|
|
171
|
+
if (bridgeSetCookies.length) {
|
|
172
|
+
extractedCookies = mergeCookies(extractedCookies, parseCookiePairsFromSetCookie(bridgeSetCookies).join("; "));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch (_) { }
|
|
94
176
|
const data = {
|
|
95
177
|
cookies: extractedCookies,
|
|
96
178
|
statusCode: response.status_code,
|
|
@@ -102,14 +184,30 @@ async function validatePassword({ identifier, digest, password, }) {
|
|
|
102
184
|
const setCookieHeaders = (typeof res.headers.getSetCookie === "function"
|
|
103
185
|
? res.headers.getSetCookie()
|
|
104
186
|
: [res.headers.get("set-cookie")].filter(Boolean));
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
187
|
+
const loginCookies = parseCookiePairsFromSetCookie(setCookieHeaders).join("; ");
|
|
188
|
+
let extractedCookies = mergeCookies(seed.cookie, loginCookies);
|
|
189
|
+
try {
|
|
190
|
+
const redirectUrl = new URL(location, "https://academia.srmist.edu.in").toString();
|
|
191
|
+
const bridgeRes = await fetch(redirectUrl, {
|
|
192
|
+
method: "GET",
|
|
193
|
+
headers: {
|
|
194
|
+
accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
195
|
+
"accept-language": "en-US,en;q=0.9",
|
|
196
|
+
cookie: extractedCookies,
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
const bridgeSetCookies = (typeof bridgeRes.headers.getSetCookie === "function"
|
|
200
|
+
? bridgeRes.headers.getSetCookie()
|
|
201
|
+
: [bridgeRes.headers.get("set-cookie")].filter(Boolean));
|
|
202
|
+
if (bridgeSetCookies.length) {
|
|
203
|
+
extractedCookies = mergeCookies(extractedCookies, parseCookiePairsFromSetCookie(bridgeSetCookies).join("; "));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch (_) { }
|
|
207
|
+
if (extractedCookies.length > 1) {
|
|
110
208
|
return {
|
|
111
209
|
data: {
|
|
112
|
-
cookies:
|
|
210
|
+
cookies: extractedCookies,
|
|
113
211
|
statusCode: 201,
|
|
114
212
|
},
|
|
115
213
|
isAuthenticated: true,
|
package/dist/src/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export type { PasswordInput, UserResponse, AuthResult, UserValidationResult, Log
|
|
|
3
3
|
export declare function verifyUser(username: string): Promise<UserValidationResult>;
|
|
4
4
|
export declare function verifyPassword({ identifier, digest, password, }: PasswordInput): Promise<AuthResult>;
|
|
5
5
|
export declare function logoutUser(cookie: string): Promise<LogoutResponse>;
|
|
6
|
-
export interface TerminateSessionsInput { flowId: string | null; identifier: string; digest: string; csrfToken?: string; }
|
|
6
|
+
export interface TerminateSessionsInput { flowId: string | null; identifier: string; digest: string; csrfToken?: string; sessionCookie?: string; }
|
|
7
7
|
export interface TerminateSessionsResult { success: boolean; status?: number; strategy?: string; data?: unknown; error?: string; errorReason?: unknown; }
|
|
8
8
|
export declare function terminateSessions(params: TerminateSessionsInput): Promise<TerminateSessionsResult>;
|
|
9
9
|
export declare function getTimetable(cookie: string): Promise<TimetableResponse>;
|
|
@@ -13,4 +13,4 @@ export declare function getUserInfo(cookie: string): Promise<UserInfoResponse>;
|
|
|
13
13
|
export declare function getCalendar(cookie: string): Promise<CalendarResponse>;
|
|
14
14
|
export declare function getDayOrder(cookie: string): Promise<DayOrderResponse>;
|
|
15
15
|
export declare function getCourse(cookie: string): Promise<CourseResponse>;
|
|
16
|
-
//# sourceMappingURL=index.d.ts.map
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.js
CHANGED
|
@@ -42,8 +42,8 @@ async function logoutUser(cookie) {
|
|
|
42
42
|
return await (0, fetchLogout_1.fetchLogout)(cookie);
|
|
43
43
|
}
|
|
44
44
|
// Terminate concurrent sessions (2-device limit bypass)
|
|
45
|
-
async function terminateSessions({ flowId, identifier, digest, csrfToken }) {
|
|
46
|
-
return await (0, terminateSessions_1.terminateSessions)({ flowId, identifier, digest, csrfToken });
|
|
45
|
+
async function terminateSessions({ flowId, identifier, digest, csrfToken, sessionCookie }) {
|
|
46
|
+
return await (0, terminateSessions_1.terminateSessions)({ flowId, identifier, digest, csrfToken, sessionCookie });
|
|
47
47
|
}
|
|
48
48
|
// Get TimeTable
|
|
49
49
|
async function getTimetable(cookie) {
|