summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.env.example2
-rw-r--r--src/aggietime.js23
-rw-r--r--src/axios_client.js2
-rw-r--r--src/constants.js20
-rw-r--r--src/main.js6
-rw-r--r--src/session.js221
6 files changed, 98 insertions, 176 deletions
diff --git a/src/.env.example b/src/.env.example
new file mode 100644
index 0000000..beac898
--- /dev/null
+++ b/src/.env.example
@@ -0,0 +1,2 @@
+A_NUMBER=A12345671
+PASSWORD= \ No newline at end of file
diff --git a/src/aggietime.js b/src/aggietime.js
index 2cc69e3..b887e36 100644
--- a/src/aggietime.js
+++ b/src/aggietime.js
@@ -9,6 +9,7 @@ import {
OPEN_SHIFT_EXP_SEC,
} from "./constants.js";
+import { with_exponential_retry } from "./exponential_retry.js";
import { client } from "./axios_client.js";
import expireCache from "expire-cache";
@@ -34,16 +35,18 @@ const get_user_position_or_specified = async (position) => {
export const get_user_info = async () => {
if (!expireCache.get("user")) {
- const user = await aggietime.get(USER_PATH).then(({ data, config }) => {
- const csrf_token = config.jar
- .toJSON()
- .cookies.find(
- ({ domain, key }) =>
- domain === AGGIETIME_DOMAIN && key === "XSRF-TOKEN"
- ).value;
- expireCache.set("aggietime-csrf", csrf_token);
- return data;
- });
+ const user = await with_exponential_retry(() =>
+ aggietime.get(USER_PATH).then(({ data, config }) => {
+ const csrf_token = config.jar
+ .toJSON()
+ .cookies.find(
+ ({ domain, key }) =>
+ domain === AGGIETIME_DOMAIN && key === "XSRF-TOKEN"
+ ).value;
+ expireCache.set("aggietime-csrf", csrf_token);
+ return data;
+ })
+ );
expireCache.set("user", user, USER_CACHE_EXP_SEC);
}
diff --git a/src/axios_client.js b/src/axios_client.js
index fd7325c..4decab0 100644
--- a/src/axios_client.js
+++ b/src/axios_client.js
@@ -2,5 +2,5 @@ import { wrapper } from "axios-cookiejar-support";
import { CookieJar } from "tough-cookie";
import axios from "axios";
-const jar = new CookieJar();
+export const jar = new CookieJar();
export const client = wrapper(axios.create({ jar, withCredentials: true }));
diff --git a/src/constants.js b/src/constants.js
index 1f8a8e0..b2e3f9a 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -3,28 +3,24 @@ export const KILL_SIGNALS = ["SIGINT", "SIGTERM", "SIGQUIT"];
export const AGGIETIME_DOMAIN = "aggietimeultra.usu.edu";
export const AGGIETIME_URI = `https://${AGGIETIME_DOMAIN}`;
+export const AGGIETIME_AUTH_COOKIE_NAME = "access_token_cookie";
+export const AGGIETIME_URL_CONTAINS_SIGNIFIES_AUTH_COMPLETE = "employee";
+
export const REFRESH_JWT_MS = 5 * 1000 * 60;
export const LOGIN_PATH = "api/v1/auth/login";
-export const LOGOUT_PATH = "api/v1/auth/logout";
export const CLOCKIN_PATH = "api/v1/positions/:position/clock_in";
export const CLOCKOUT_PATH = "api/v1/positions/:position/clock_out";
export const USER_PATH = "api/v1/auth/get_user_info";
export const OPEN_SHIFT_PATH = "api/v1/users/:anumber/open_shift";
export const OPEN_SHIFT_EXP_SEC = 60;
-export const EXECUTION_SELECTOR = "input[type=hidden][name=execution]";
-export const DUO_IFRAME_SELECTOR = "#duo_iframe";
-export const DUO_FACTOR = "Duo Push";
-export const DUO_INPUT_FIELD_SELECTORS = [
- "input[type=hidden][name=sid]",
- "input[type=hidden][name=out_of_date]",
- "input[type=hidden][name=days_out_of_date]",
- "input[type=hidden][name=days_to_block]",
- "input[type=hidden][name=preferred_device]",
-];
-
export const USER_CACHE_EXP_SEC = 30;
+export const SAML_SIGN_IN_TITLE = "Sign in to your account";
+export const SAML_SUBMIT_SELECTOR = "input[type=submit]";
+export const SAML_EMAIL_SELECTOR = "input[type=email]";
+export const SAML_PASSWORD_SELECTOR = "input[type=password]";
+
export const MAX_DEFAULT_RETRY_AMOUNT = 3;
export const WAIT_MS = 2000;
export const RETRY_EXPONENT = 1.2;
diff --git a/src/main.js b/src/main.js
index c81a471..4b8a1bf 100644
--- a/src/main.js
+++ b/src/main.js
@@ -5,7 +5,6 @@ import {
KILL_SIGNALS,
REFRESH_JWT_MS,
} from "./constants.js";
-import { with_exponential_retry } from "./exponential_retry.js";
import * as actions from "./actions.js";
import * as session from "./session.js";
import * as argparse from "argparse";
@@ -87,9 +86,8 @@ specify another socket path with --socket_path`
process.exit(1);
}
- await with_exponential_retry(() =>
- session.login(process.env.A_NUMBER, process.env.PASSWORD)
- );
+ await session.login(process.env.A_NUMBER, process.env.PASSWORD);
+
session.refresh_jwt();
setInterval(session.refresh_jwt, REFRESH_JWT_MS);
diff --git a/src/session.js b/src/session.js
index a127ac2..aee49c2 100644
--- a/src/session.js
+++ b/src/session.js
@@ -1,112 +1,19 @@
+import { Builder, Browser, By, Key, until } from "selenium-webdriver";
+import { Cookie } from "tough-cookie";
+
import {
+ AGGIETIME_AUTH_COOKIE_NAME,
+ AGGIETIME_DOMAIN,
AGGIETIME_URI,
+ AGGIETIME_URL_CONTAINS_SIGNIFIES_AUTH_COMPLETE,
LOGIN_PATH,
- LOGOUT_PATH,
- USER_PATH,
- DUO_IFRAME_SELECTOR,
- DUO_FACTOR,
- DUO_INPUT_FIELD_SELECTORS,
- EXECUTION_SELECTOR,
+ SAML_SIGN_IN_TITLE,
+ SAML_SUBMIT_SELECTOR,
+ SAML_EMAIL_SELECTOR,
+ SAML_PASSWORD_SELECTOR,
} from "./constants.js";
+import { jar } from "./axios_client.js";
import * as aggietime from "./aggietime.js";
-import { client } from "./axios_client.js";
-
-import { parse } from "node-html-parser";
-
-const make_auth_params = (username, password, execution) =>
- new URLSearchParams({
- username,
- password,
- execution,
- _eventId: "submit",
- geolocation: "",
- });
-
-const make_duo_push_params = (
- sid,
- out_of_date,
- days_out_of_date,
- days_to_block,
- device
-) =>
- new URLSearchParams({
- sid,
- out_of_date,
- days_out_of_date,
- days_to_block,
- device,
- factor: DUO_FACTOR,
- });
-
-const push_duo_get_cookie = async (
- duo_iframe_obj,
- response_url,
- username,
- password,
- execution
-) => {
- const [duo_host, duo_sig, duo_src] = [
- "data-host",
- "data-sig-request",
- "src",
- ].map((attr) => duo_iframe_obj.getAttribute(attr));
-
- const duo = client.create({
- baseURL: `https://${duo_host}`,
- });
- const transaction_id = duo_sig.split(":").at(0);
- const app = duo_sig.split(":APP").at(-1);
-
- console.log("Retrieving DUO frame DOM for this transaction...");
- const duo_frame = await duo
- .post(
- `/frame/web/v1/auth?tx=${transaction_id}&parent=${response_url}&v=2.6`
- )
- .then(({ data }) => parse(data));
-
- const push_param_list = DUO_INPUT_FIELD_SELECTORS.map((selector) =>
- duo_frame.querySelector(selector).getAttribute("value")
- );
- let [sid, _] = push_param_list;
-
- const {
- response: { txid },
- } = await duo
- .post("/frame/prompt", make_duo_push_params.apply(null, push_param_list))
- .then(({ data }) => data);
-
- console.log("Waiting for approval...");
- const { cookie, parent } = await wait_approve_duo_cookie_resp(duo, sid, txid);
- return { duo_signed_resp: cookie + ":APP" + app, parent };
-};
-
-const wait_approve_duo_cookie_resp = async (duo, sid, txid) => {
- // First status to confirm device was pushed to,
- // Second to create a long-poll connection-alive socket for approval status :3
- const status_params = new URLSearchParams({
- sid,
- txid,
- });
- const {
- response: { result_url },
- } = await duo.post("/frame/status", status_params).then(async ({ data }) => {
- if (data.stat === "OK" && data.response.status_code === "pushed")
- return await duo
- .post("/frame/status", status_params)
- .then(({ data }) => data);
- return data;
- });
-
- const {
- response: { cookie, parent },
- } = await duo
- .post(result_url, new URLSearchParams({ sid }))
- .then(({ data }) => data);
-
- if (!cookie) throw "Unable to retrieve signed cookie from DUO";
-
- return { cookie, parent };
-};
export const refresh_jwt = () => {
console.log("Refreshing JWT...");
@@ -116,51 +23,67 @@ export const refresh_jwt = () => {
export const logout = () => client.get(`${AGGIETIME_URI}/${LOGOUT_PATH}`);
-export const login = async (username, password) => {
- const login_page_promise = client.get(`${AGGIETIME_URI}/${LOGIN_PATH}`);
- console.log("Retreiving login page...");
-
- const {
- request: {
- res: { responseUrl: response_url },
- },
- } = await login_page_promise;
-
- let cas_root = await login_page_promise.then(({ data }) => parse(data));
-
- console.log("Parsing DOM for spring execution token...");
- const login_execution = cas_root
- .querySelector(EXECUTION_SELECTOR)
- .getAttribute("value");
-
- console.log("Sending CAS credentials...");
- cas_root = await client
- .post(response_url, make_auth_params(username, password, login_execution))
- .then(({ data }) => parse(data));
-
- console.log("Parsing DOM for authenticated spring execution token...");
- const authed_execution = cas_root
- .querySelector(EXECUTION_SELECTOR)
- .getAttribute("value");
-
- const duo_iframe_obj = cas_root.querySelector(DUO_IFRAME_SELECTOR);
- console.log("Starting DUO authentication...");
- const { duo_signed_resp, parent: signed_response_url } =
- await push_duo_get_cookie(
- duo_iframe_obj,
- response_url,
- username,
- password,
- login_execution
+export const login = async (a_number, password) => {
+ const driver = await new Builder().forBrowser(Browser.CHROME).build();
+ let cookie;
+
+ try {
+ console.log("Navigating to login path...");
+ await driver.get(`${AGGIETIME_URI}/${LOGIN_PATH}`);
+
+ if (a_number && password) {
+ console.log("Waiting until we eventually redirect to SAML...");
+ await driver.wait(until.titleIs(SAML_SIGN_IN_TITLE));
+
+ console.log("Waiting until email field is located...");
+ await driver.wait(until.elementLocated(By.css(SAML_EMAIL_SELECTOR)));
+
+ console.log("Filling email field...");
+ await driver
+ .findElement(By.css(SAML_EMAIL_SELECTOR))
+ .sendKeys(`${a_number}@usu.edu`);
+ await driver.findElement(By.css(SAML_SUBMIT_SELECTOR)).click();
+
+ console.log("Waiting until password field is located...");
+ await Promise.all(
+ [SAML_PASSWORD_SELECTOR, SAML_SUBMIT_SELECTOR].map((selector) =>
+ driver.wait(until.elementLocated(By.css(selector)))
+ )
+ );
+
+ console.log("Filling password...");
+ await driver
+ .findElement(By.css(SAML_PASSWORD_SELECTOR))
+ .sendKeys(password);
+
+ console.log("Debouncing a bit...");
+ await new Promise((res) => setTimeout(res, 500));
+
+ console.log("Submit!");
+ await driver.findElement(By.css(SAML_SUBMIT_SELECTOR)).click();
+ }
+
+ console.log(
+ "Waiting for aggietime response (potential DUO required here)..."
+ );
+ await driver.wait(
+ until.urlContains(AGGIETIME_URL_CONTAINS_SIGNIFIES_AUTH_COMPLETE)
+ );
+
+ console.log("Retrieving cookie...");
+ cookie = await driver.manage().getCookie(AGGIETIME_AUTH_COOKIE_NAME);
+
+ await jar.setCookie(
+ new Cookie({
+ ...cookie,
+ key: cookie.name,
+ }),
+ AGGIETIME_URI
);
+ console.log("Got it!");
+ } finally {
+ await driver.quit();
+ }
- console.log("Sending DUO signed response back to CAS...");
- return await client.post(
- signed_response_url,
- new URLSearchParams({
- execution: authed_execution,
- signedDuoResponse: duo_signed_resp,
- _eventId: "submit",
- })
- );
+ return cookie;
};