Allow passwordless registration
This commit is contained in:
parent
6ddfb5b52d
commit
227e3d3729
7 changed files with 43 additions and 13 deletions
|
@ -20,6 +20,7 @@ paths:
|
|||
- password
|
||||
- ethereum
|
||||
- eip4361
|
||||
example: eip4361
|
||||
username:
|
||||
description: User name (required if grant type is "password").
|
||||
type: string
|
||||
|
@ -75,21 +76,23 @@ paths:
|
|||
description: The desired username for the account.
|
||||
type: string
|
||||
password:
|
||||
description: The password to be used for login.
|
||||
description: The password to be used for login. Either password or EIP-4361 message must be provided.
|
||||
type: string
|
||||
example: null
|
||||
message:
|
||||
description: EIP-4361 message
|
||||
type: string
|
||||
example: "example.com wants you to sign in with your Ethereum account:"
|
||||
signature:
|
||||
description: EIP-4361 signature (required if message is present)
|
||||
type: string
|
||||
example: 0x905...
|
||||
invite_code:
|
||||
description: Invite code
|
||||
type: string
|
||||
example: 9b288bfa7dc75fff53e98aa4d76e77d5
|
||||
required:
|
||||
- username
|
||||
- password
|
||||
responses:
|
||||
201:
|
||||
description: Successful operation
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE user_account ALTER COLUMN password_hash DROP NOT NULL;
|
|
@ -23,7 +23,7 @@ CREATE TABLE user_invite_code (
|
|||
CREATE TABLE user_account (
|
||||
id UUID PRIMARY KEY REFERENCES actor_profile (id) ON DELETE CASCADE,
|
||||
wallet_address VARCHAR(100) UNIQUE,
|
||||
password_hash VARCHAR(200) NOT NULL,
|
||||
password_hash VARCHAR(200),
|
||||
private_key TEXT NOT NULL,
|
||||
invite_code VARCHAR(100) UNIQUE REFERENCES user_invite_code (code) ON DELETE SET NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
|
|
|
@ -104,7 +104,7 @@ impl Account {
|
|||
#[derive(Deserialize)]
|
||||
pub struct AccountCreateData {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub password: Option<String>,
|
||||
|
||||
pub message: Option<String>,
|
||||
pub signature: Option<String>,
|
||||
|
@ -117,6 +117,9 @@ impl AccountCreateData {
|
|||
pub fn clean(&self) -> Result<(), ValidationError> {
|
||||
validate_username(&self.username)?;
|
||||
validate_local_username(&self.username)?;
|
||||
if self.password.is_none() && self.message.is_none() {
|
||||
return Err(ValidationError("password or EIP-4361 message is required"));
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -212,6 +215,19 @@ mod tests {
|
|||
|
||||
const INSTANCE_URL: &str = "https://example.com";
|
||||
|
||||
#[test]
|
||||
fn test_validate_account_create_data() {
|
||||
let account_data = AccountCreateData {
|
||||
username: "test".to_string(),
|
||||
password: None,
|
||||
message: None,
|
||||
signature: Some("test".to_string()),
|
||||
invite_code: None,
|
||||
};
|
||||
let error = account_data.clean().unwrap_err();
|
||||
assert_eq!(error.to_string(), "password or EIP-4361 message is required");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_account_from_profile() {
|
||||
let profile = DbActorProfile {
|
||||
|
|
|
@ -72,6 +72,14 @@ pub async fn create_account(
|
|||
return Err(ValidationError("invalid invite code").into());
|
||||
}
|
||||
}
|
||||
|
||||
let password_hash = if let Some(password) = account_data.password.as_ref() {
|
||||
let password_hash = hash_password(password)
|
||||
.map_err(|_| HttpError::InternalError)?;
|
||||
Some(password_hash)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let wallet_address = if let Some(message) = account_data.message.as_ref() {
|
||||
let signature = account_data.signature.as_ref()
|
||||
.ok_or(ValidationError("signature is required"))?;
|
||||
|
@ -85,8 +93,10 @@ pub async fn create_account(
|
|||
} else {
|
||||
None
|
||||
};
|
||||
assert!(password_hash.is_some() || wallet_address.is_some());
|
||||
|
||||
if let Some(blockchain_config) = config.blockchain.as_ref() {
|
||||
// Wallet address is required only if blockchain integration is enabled
|
||||
// Wallet address is required if blockchain integration is enabled
|
||||
let wallet_address = wallet_address.as_ref()
|
||||
.ok_or(ValidationError("wallet address is required"))?;
|
||||
let is_allowed = is_allowed_user(blockchain_config, wallet_address).await
|
||||
|
@ -95,9 +105,7 @@ pub async fn create_account(
|
|||
return Err(ValidationError("not allowed to sign up").into());
|
||||
}
|
||||
}
|
||||
// Hash password and generate private key
|
||||
let password_hash = hash_password(&account_data.password)
|
||||
.map_err(|_| HttpError::InternalError)?;
|
||||
// Generate RSA private key for actor
|
||||
let private_key = match web::block(generate_private_key).await {
|
||||
Ok(private_key) => private_key,
|
||||
Err(_) => return Err(HttpError::InternalError),
|
||||
|
|
|
@ -58,9 +58,11 @@ async fn token_view(
|
|||
if request_data.grant_type == "password" || request_data.grant_type == "ethereum" {
|
||||
let password = request_data.password.as_ref()
|
||||
.ok_or(ValidationError("password is required"))?;
|
||||
let password_hash = user.password_hash.as_ref()
|
||||
.ok_or(ValidationError("password auth is disabled"))?;
|
||||
let password_correct = verify_password(
|
||||
&user.password_hash,
|
||||
&password,
|
||||
password_hash,
|
||||
password,
|
||||
).map_err(|_| HttpError::InternalError)?;
|
||||
if !password_correct {
|
||||
return Err(ValidationError("incorrect password").into());
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::models::profiles::types::DbActorProfile;
|
|||
pub struct DbUser {
|
||||
pub id: Uuid,
|
||||
pub wallet_address: Option<String>,
|
||||
pub password_hash: String,
|
||||
pub password_hash: Option<String>,
|
||||
pub private_key: String,
|
||||
pub invite_code: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
|
@ -23,7 +23,7 @@ pub struct DbUser {
|
|||
pub struct User {
|
||||
pub id: Uuid,
|
||||
pub wallet_address: Option<String>,
|
||||
pub password_hash: String,
|
||||
pub password_hash: Option<String>,
|
||||
pub private_key: String,
|
||||
pub profile: DbActorProfile,
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ impl User {
|
|||
#[cfg_attr(test, derive(Default))]
|
||||
pub struct UserCreateData {
|
||||
pub username: String,
|
||||
pub password_hash: String,
|
||||
pub password_hash: Option<String>,
|
||||
pub private_key_pem: String,
|
||||
pub wallet_address: Option<String>,
|
||||
pub invite_code: Option<String>,
|
||||
|
|
Loading…
Reference in a new issue