Skip to content

Commit

Permalink
feature: local login page
Browse files Browse the repository at this point in the history
  • Loading branch information
densumesh authored and cdxker committed Apr 12, 2024
1 parent b38a364 commit 596f8d6
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 10 deletions.
15 changes: 7 additions & 8 deletions server/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ path = "src/bin/create-new-qdrant.rs"


[dependencies]
actix-identity = { version = "0.6.0" }
actix-session = { version = "0.8.0", features = [
actix-identity = { version = "0.7.1" }
actix-session = { version = "0.9.0", features = [
"redis-rs-session",
"redis-rs-tls-session",
] }
Expand Down
9 changes: 9 additions & 0 deletions server/src/handlers/auth_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use openidconnect::core::{CoreAuthenticationFlow, CoreClient, CoreProviderMetada
use openidconnect::{AccessTokenHash, ClientId, IssuerUrl, Nonce};
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::fs::read_to_string;
use std::future::{ready, Ready};
use utoipa::{IntoParams, ToSchema};

Expand Down Expand Up @@ -558,3 +559,11 @@ pub async fn get_me(
pub async fn health_check() -> Result<HttpResponse, actix_web::Error> {
Ok(HttpResponse::Ok().finish())
}

/// Local login page for cli
pub async fn login_cli() -> Result<HttpResponse, ServiceError> {
let html_page = read_to_string("src/public/login.html").map_err(|e| {
ServiceError::InternalServerError(format!("Could not read login page {}", e.to_string()))
})?;
Ok(HttpResponse::Ok().content_type("text/html").body(html_page))
}
4 changes: 4 additions & 0 deletions server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,10 @@ pub async fn main() -> std::io::Result<()> {
.service(
web::redirect("/swagger-ui", "/swagger-ui/")
)
.service(
web::resource("/auth/cli")
.route(web::get().to(handlers::auth_handler::login_cli))
)
// everything under '/api/' route
.service(
web::scope("/api")
Expand Down
144 changes: 144 additions & 0 deletions server/src/public/login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Create API Key</title>
<style>
body {
font-family: "Arial", sans-serif;
text-align: center;
margin-top: 50px;
display: flex;
flex-direction: column;
align-items: center;
}
.container {
width: 100%;
max-width: 400px;
padding: 20px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
border-radius: 5px;
}
input,
select,
button {
width: 100%;
padding: 10px;
margin: 10px 0;
border: 1px solid #ddd;
border-radius: 5px;
}
.api-key-container {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
margin-top: 10px;
}
#apiKey {
font-weight: bold;
color: #a33eb5;
}
#copyButton {
background-color: #ffffff;
border: none;
padding: 5px;
border-radius: 5px;
cursor: pointer;
display: flex;
align-items: center;
width: 30px;
height: 30px;
}
#copyButton:hover {
background-color: #e2e2e2;
}
.copy-icon .check-icon {
fill: #000000;
width: 10px;
height: 10px;
}
</style>
</head>
<body>
<div class="container">
<form id="apiKeyForm">
<input type="text" id="name" placeholder="Name" required />
<select id="role" required>
<option value="" disabled selected>Select Role</option>
<option value="0">Read Only</option>
<option value="1">Read and Write</option>
</select>
<button type="submit">Create API Key</button>
</form>
<div id="result" style="display: none">
<p>Paste this into your terminal:</p>
<div class="api-key-container">
<span id="apiKey"></span>
<button id="copyButton" aria-label="Copy API Key">
<svg class="copy-icon" viewBox="0 0 24 24">
<!-- Clipboard Icon -->
<path
d="M19,21H9V7H19M19,5H9A2,2 0 0,0 7,7V21A2,2 0 0,0 9,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path>
</svg>
<svg class="check-icon" viewBox="0 0 24 24" style="display: none">
<!-- Check Icon -->
<path
d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path>
</svg>
</button>
</div>
</div>
</div>

<script>
document
.getElementById("apiKeyForm")
.addEventListener("submit", function (e) {
e.preventDefault();
const name = document.getElementById("name").value;
const role = document.getElementById("role").value;
const params = new URLSearchParams(window.location.search);
const host = params.get("host");

fetch(`${host}/api/user/api_key`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, role: parseInt(role) }),
})
.then((response) => response.json())
.then((data) => {
document.getElementById("apiKeyForm").style.display = "none";
document.getElementById("result").style.display = "block";
document.getElementById("apiKey").textContent = data.api_key;
})
.catch((error) => console.error("Error:", error));

document
.getElementById("copyButton")
.addEventListener("click", function () {
const apiKey = document.getElementById("apiKey").textContent;
navigator.clipboard
.writeText(apiKey)
.then(() => {
// Hide the clipboard icon and show the check icon
const copyIcon = document.querySelector(".copy-icon");
const checkIcon = document.querySelector(".check-icon");
copyIcon.style.display = "none";
checkIcon.style.display = "inline";

// After 1 second, revert the icons
setTimeout(() => {
copyIcon.style.display = "inline";
checkIcon.style.display = "none";
}, 1000);
})
.catch((err) => {
console.error("Error in copying text: ", err);
});
});
});
</script>
</body>
</html>

0 comments on commit 596f8d6

Please sign in to comment.