feat: add command login

Signed-off-by: sisungo <[email protected]>
This commit is contained in:
2026-09-11 09:04:08 +00:00
parent ccd4288c79
commit 418305653c
9 changed files with 147 additions and 19 deletions
-4
View File
@@ -1,10 +1,6 @@
use crate::{client, util::uuid_by_one_user_filter};
use anyhow::anyhow;
use clap::Parser;
use semios_account::{
auth::{AuthCli, AuthenticateArgs, UserAuthMethodFlags},
protocol::{GetAuthMethodPathArgs, GetUserAuthMethodsArgs},
};
#[derive(Debug, Clone, Parser)]
pub struct Cli {
+109
View File
@@ -0,0 +1,109 @@
use crate::client;
use semios_account::{protocol::GetUserInfoArgs, wellknown::CLI_LOGIN_SHELL};
use std::os::unix::process::CommandExt;
#[derive(Debug)]
struct Cli {
preserve_environment: bool,
user: Option<String>,
host: Option<String>,
force: bool,
}
impl Cli {
const USAGE: &str = "usage: login [-p] [-h host] [-f] [name]";
fn parse() -> Self {
let mut args = std::env::args().skip(1);
let mut this = Self {
preserve_environment: false,
user: None,
host: None,
force: false,
};
while let Some(i) = args.next() {
if i == "-f" {
this.force = true;
} else if i == "-p" {
this.preserve_environment = true;
} else if i == "-h" {
this.host = Some(args.next().unwrap_or_else(|| Self::parse_error()));
} else if !i.starts_with("-") {
this.user = Some(i);
} else {
Self::parse_error()
}
}
this
}
fn parse_error() -> ! {
eprintln!("{}", Self::USAGE);
std::process::exit(1);
}
}
pub fn main() {
let cli = Cli::parse();
let mut client = match client() {
Ok(val) => val,
Err(err) => {
eprintln!("login: Service error: {err}");
std::process::exit(1);
}
};
loop {
let user = cli.user.clone().unwrap_or_else(ask_login);
let Ok(user_info) = client.get_user_info(GetUserInfoArgs::Username(user.clone())) else {
eprintln!("\nLogin incorrect");
std::process::exit(1);
};
let auth_result =
crate::util::authenticate(&mut client, user_info.uuid, Some("password".into()), "CLI");
match auth_result {
Ok(true) => (),
Ok(false) => std::process::exit(1),
Err(_) => {
eprintln!("\nLogin incorrect");
continue;
}
};
let Some(uid) = user_info.host_uid else {
eprintln!("\nThis user is not configured to login on this host.");
continue;
};
let Some(shell) = user_info.defaults.get(CLI_LOGIN_SHELL) else {
eprintln!("\nThis user has no login shell.");
continue;
};
let home_dir = user_info.home_directory.unwrap_or_else(|| "/".into());
if !crate::has_root_privs() {
eprintln!("login: the `login` binary is only available with root privileges");
continue;
}
// Set environment variables
if !cli.preserve_environment {
// SAFETY: The program is single-threaded, so no data race is possible here.
unsafe {
std::env::set_var("HOME", &home_dir);
std::env::set_var("USER", &user);
std::env::set_var("SHELL", &shell);
std::env::set_var("LOGNAME", &user);
}
}
let err = std::process::Command::new(shell).uid(uid).exec();
eprintln!("login: exec error: {err}");
}
}
fn ask_login() -> String {
eprint!("login: ");
let mut login = String::with_capacity(128);
_ = std::io::stdin().read_line(&mut login);
login.trim().into()
}
+5 -1
View File
@@ -2,6 +2,7 @@ mod authenticate;
mod create_group;
mod create_user;
mod info;
mod login;
mod su;
mod update_auth;
mod util;
@@ -31,7 +32,10 @@ fn main() {
// Invoke non-default utility if required
if progname() == "su" {
su::main();
std::process::exit(0);
return;
} else if progname() == "login" {
login::main();
return;
}
// Invoke the default `account` utility
+1 -2
View File
@@ -1,6 +1,5 @@
use semios_account::{protocol::GetUserInfoArgs, wellknown::CLI_LOGIN_SHELL};
use crate::client;
use semios_account::{protocol::GetUserInfoArgs, wellknown::CLI_LOGIN_SHELL};
use std::{ffi::OsString, os::unix::process::CommandExt};
#[derive(Debug)]
+1 -1
View File
@@ -12,7 +12,7 @@ anyhow = "1"
cfg-if = "1"
bitflags = "2"
clap = { workspace = true }
rusqlite = "0.39"
rusqlite = { version = "0.39", features = ["bundled"] }
rustc-hash = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
-1
View File
@@ -1 +0,0 @@
// Sandbox rules for running an authentication method.
+9 -9
View File
@@ -154,7 +154,7 @@ impl Session {
async fn list_user(
state: Arc<AppState>,
caller: Caller,
_caller: Caller,
args: ListUserArgs,
) -> Result<Vec<Uuid>, Error> {
Ok(state.local_db.list_user(args.start, args.len))
@@ -162,7 +162,7 @@ async fn list_user(
async fn list_group(
state: Arc<AppState>,
caller: Caller,
_caller: Caller,
args: ListGroupArgs,
) -> Result<Vec<Uuid>, Error> {
Ok(state.local_db.list_group(args.start, args.len))
@@ -170,7 +170,7 @@ async fn list_group(
async fn get_user_info(
state: Arc<AppState>,
caller: Caller,
_caller: Caller,
args: GetUserInfoArgs,
) -> Result<UserInfo, Error> {
let uuid = match args {
@@ -189,7 +189,7 @@ async fn get_user_info(
async fn get_group_info(
state: Arc<AppState>,
caller: Caller,
_caller: Caller,
args: GetGroupInfoArgs,
) -> Result<GroupInfo, Error> {
let uuid = match args {
@@ -279,7 +279,7 @@ async fn create_group(
async fn remove_user(
state: Arc<AppState>,
caller: Caller,
args: RemoveUserArgs,
_args: RemoveUserArgs,
) -> Result<(), Error> {
require_secure_tags(&state, caller, &[SecureTag::RemoveUser]).await?;
todo!();
@@ -321,7 +321,7 @@ async fn set_secret(
async fn get_user_auth_methods(
state: Arc<AppState>,
caller: Caller,
_caller: Caller,
args: GetUserAuthMethodsArgs,
) -> Result<Vec<UserAuthMethodRecord>, Error> {
require_secure_tags(&state, Caller::User(args.user), &[SecureTag::Login]).await?;
@@ -330,7 +330,7 @@ async fn get_user_auth_methods(
async fn user_add_auth_method(
state: Arc<AppState>,
caller: Caller,
_caller: Caller,
args: UserAddAuthMethodArgs,
) -> Result<(), Error> {
require_secure_tags(&state, Caller::User(args.user), &[SecureTag::Login]).await?;
@@ -355,7 +355,7 @@ async fn user_add_auth_method(
async fn get_auth_method_path(
state: Arc<AppState>,
caller: Caller,
_caller: Caller,
args: GetAuthMethodPathArgs,
) -> Result<PathBuf, Error> {
Ok(state
@@ -368,7 +368,7 @@ async fn get_auth_method_path(
// ==- Helpers -==
async fn require_same_user(
state: &AppState,
_state: &AppState,
caller: Caller,
requested_user: Uuid,
) -> Result<(), Error> {
+1 -1
View File
@@ -1,6 +1,6 @@
use crate::{
error::Error,
record::{GroupInfo, HostGid, HostUid, SecureTag},
record::{HostGid, HostUid, SecureTag},
secret::{MasterKeyProvision, Secret},
};
use cfg_if::cfg_if;
+21
View File
@@ -0,0 +1,21 @@
[
{
"on_failure": "terminate",
"request": {
"method": "CreateUser",
"params": {
"username": "root",
"fullname": "System Administrator",
"host_uid": { "manual": 0 },
"description": "UNIX system administrator",
"secure_tags": ["Login", "CreateUser", "RemoveUser", "ReadSecret", "WriteSecret"],
"extra_records": {},
"defaults": {
"CliLoginShell": "/bin/sh"
},
"home_directory": "/home/root",
"groups": []
}
}
}
]