-
Notifications
You must be signed in to change notification settings - Fork 31
Expand file tree
/
Copy pathindex.ts
More file actions
102 lines (94 loc) · 3.46 KB
/
index.ts
File metadata and controls
102 lines (94 loc) · 3.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import type { AuthContextOutput } from "./types.d.js";
import crypto from "node:crypto";
import process from "node:process";
import * as url from "node:url";
import core from "@actions/core";
import playwright from "playwright";
export default async function () {
core.info("Starting 'auth' action");
let browser: playwright.Browser | undefined;
let context: playwright.BrowserContext | undefined;
let page: playwright.Page | undefined;
try {
// Get inputs
const loginUrl = core.getInput("login_url", { required: true });
const username = core.getInput("username", { required: true });
const password = core.getInput("password", { required: true });
core.setSecret(password);
// Determine storage path for authenticated session state
// Playwright will create missing directories, if needed
const actionDirectory = `${url.fileURLToPath(new URL(import.meta.url))}/..`;
const sessionStatePath = `${
process.env.RUNNER_TEMP ?? actionDirectory
}/.auth/${crypto.randomUUID()}/sessionState.json`;
// Launch a headless browser
browser = await playwright.chromium.launch({
headless: true,
executablePath: process.env.CI ? "/usr/bin/google-chrome" : undefined,
});
context = await browser.newContext({
// Try HTTP Basic authentication
httpCredentials: {
username,
password,
},
});
page = await context.newPage();
// Navigate to login page
core.info("Navigating to login page");
await page.goto(loginUrl);
// Check for a login form.
// If no login form is found, then either HTTP Basic auth succeeded, or the page does not require authentication.
core.info("Checking for login form");
const [usernameField, passwordField] = await Promise.all([
page.getByLabel(/user ?name/i).first(),
page.getByLabel(/password/i).first(),
]);
const [usernameFieldExists, passwordFieldExists] = await Promise.all([
usernameField.count(),
passwordField.count(),
]);
if (usernameFieldExists && passwordFieldExists) {
// Try form authentication
core.info("Filling username");
await usernameField.fill(username);
core.info("Filling password");
await passwordField.fill(password);
core.info("Logging in");
await page
.getByLabel(/password/i)
.locator("xpath=ancestor::form")
.evaluate((form) => (form as HTMLFormElement).submit());
} else {
core.info("No login form detected");
// This occurs if HTTP Basic auth succeeded, or if the page does not require authentication.
}
// Output authenticated session state
const { cookies, origins } = await context.storageState();
const authContextOutput: AuthContextOutput = {
username,
password,
cookies,
localStorage: origins.reduce((acc, { origin, localStorage }) => {
acc[origin] = localStorage.reduce((acc, { name, value }) => {
acc[name] = value;
return acc;
}, {} as Record<string, string>);
return acc;
}, {} as Record<string, Record<string, string>>),
};
core.setOutput("auth_context", JSON.stringify(authContextOutput));
core.debug("Output: 'auth_context'");
} catch (error) {
if (page) {
core.info(`Errored at page URL: ${page.url()}`);
}
core.setFailed(`${error}`);
process.exit(1);
} finally {
// Clean up
await context?.close();
await browser?.close();
}
core.info("Finished 'auth' action");
}