Skip to content

Commit

Permalink
feat(ee): add name field for user model, add api to update it (#2100)
Browse files Browse the repository at this point in the history
* feat(ee): add `name` field for user model, add api to update it

* update schema.svg

* add name check

* [autofix.ci] apply automated fixes

* apply validator for username update input

* update regex for username validation

* resolve comment

* [autofix.ci] apply automated fixes

* update regex for name validation

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
  • Loading branch information
darknight and autofix-ci[bot] authored May 16, 2024
1 parent 73929bd commit b040d22
Show file tree
Hide file tree
Showing 13 changed files with 118 additions and 2 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,13 @@ brew install protobuf
apt-get install protobuf-compiler libopenblas-dev
```

3. Now, you can build Tabby by running the command `cargo build`.
3. Install useful tools:
```bash
# For Ubuntu
sudo apt install make sqlite3 graphviz
```

4. Now, you can build Tabby by running the command `cargo build`.

### Start Hacking!
... and don't forget to submit a [Pull Request](https://github.com/TabbyML/tabby/compare)
Expand Down
1 change: 1 addition & 0 deletions ee/tabby-db/migrations/0030_user-name.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE users DROP COLUMN name;
1 change: 1 addition & 0 deletions ee/tabby-db/migrations/0030_user-name.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE users ADD COLUMN name VARCHAR(255);
Binary file modified ee/tabby-db/schema.sqlite
Binary file not shown.
1 change: 1 addition & 0 deletions ee/tabby-db/schema/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ CREATE TABLE users(
active BOOLEAN NOT NULL DEFAULT 1,
password_encrypted VARCHAR(128),
avatar BLOB DEFAULT NULL,
name VARCHAR(255),
CONSTRAINT `idx_email` UNIQUE(`email`)
CONSTRAINT `idx_auth_token` UNIQUE(`auth_token`)
);
Expand Down
4 changes: 4 additions & 0 deletions ee/tabby-db/schema/schema.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 24 additions & 1 deletion ee/tabby-db/src/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub struct UserDAO {

pub id: i64,
pub email: String,
pub name: Option<String>,
pub password_encrypted: Option<String>,
pub is_admin: bool,

Expand All @@ -28,7 +29,7 @@ macro_rules! select {
($str:literal $(,)? $($val:expr),*) => {
query_as!(
UserDAO,
r#"SELECT id as "id!", email, password_encrypted, is_admin, created_at as "created_at!", updated_at as "updated_at!", auth_token, active FROM users WHERE "# + $str,
r#"SELECT id as "id!", email, name, password_encrypted, is_admin, created_at as "created_at!", updated_at as "updated_at!", auth_token, active FROM users WHERE "# + $str,
$($val),*
)
}
Expand Down Expand Up @@ -123,6 +124,7 @@ impl DbConn {
[
"id"!,
"email",
"name",
"password_encrypted",
"is_admin",
"created_at"!,
Expand Down Expand Up @@ -256,6 +258,13 @@ impl DbConn {
})
.await
}

pub async fn update_user_name(&self, id: i64, name: String) -> Result<()> {
query!("UPDATE users SET name = ? WHERE id = ?;", name, id)
.execute(&self.pool)
.await?;
Ok(())
}
}

fn generate_auth_token() -> String {
Expand Down Expand Up @@ -292,6 +301,20 @@ mod tests {
assert!(conn.update_user_active(id, false).await.is_err());
}

#[tokio::test]
async fn test_update_user_name() {
let conn = DbConn::new_in_memory().await.unwrap();
let id = create_user(&conn).await;

let user = conn.get_user(id).await.unwrap().unwrap();
assert_eq!(user.name, None);

conn.update_user_name(id, "test".into()).await.unwrap();

let user = conn.get_user(id).await.unwrap().unwrap();
assert_eq!(user.name, Some("test".into()));
}

#[tokio::test]
async fn test_get_user_by_email() {
let conn = DbConn::new_in_memory().await.unwrap();
Expand Down
2 changes: 2 additions & 0 deletions ee/tabby-schema/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ type Mutation {
updateUserActive(id: ID!, active: Boolean!): Boolean!
updateUserRole(id: ID!, isAdmin: Boolean!): Boolean!
uploadUserAvatarBase64(id: ID!, avatarBase64: String): Boolean!
updateUserName(id: ID!, name: String!): Boolean!
register(email: String!, password1: String!, password2: String!, invitationCode: String): RegisterResponse!
tokenAuth(email: String!, password: String!): TokenAuthResponse!
verifyToken(token: String!): Boolean!
Expand Down Expand Up @@ -445,6 +446,7 @@ type TokenAuthResponse {
type User {
id: ID!
email: String!
name: String!
isAdmin: Boolean!
isOwner: Boolean!
authToken: String!
Expand Down
1 change: 1 addition & 0 deletions ee/tabby-schema/src/dao.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ impl From<UserDAO> for auth::User {
auth::User {
id: val.id.as_id(),
email: val.email,
name: val.name.unwrap_or_default(),
is_owner,
is_admin: val.is_admin,
auth_token: val.auth_token,
Expand Down
18 changes: 18 additions & 0 deletions ee/tabby-schema/src/schema/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ impl JWTPayload {
pub struct User {
pub id: juniper::ID,
pub email: String,
pub name: String,
pub is_admin: bool,
pub is_owner: bool,
pub auth_token: String,
Expand Down Expand Up @@ -278,6 +279,22 @@ pub struct PasswordChangeInput {
pub new_password2: String,
}

#[derive(Validate)]
pub struct UpdateUserNameInput {
#[validate(length(min = 2, code = "name", message = "Name must be at least 2 characters"))]
#[validate(length(
max = 20,
code = "name",
message = "Name must be at most 20 characters"
))]
#[validate(regex(
code = "name",
path = "crate::schema::constants::USERNAME_REGEX",
message = "Invalid name, name may contain numbers or special characters which are not supported"
))]
pub name: String,
}

#[derive(Debug, Serialize, Deserialize, GraphQLObject)]
#[graphql(context = Context)]
pub struct Invitation {
Expand Down Expand Up @@ -406,6 +423,7 @@ pub trait AuthenticationService: Send + Sync {
async fn update_user_role(&self, id: &ID, is_admin: bool) -> Result<()>;
async fn update_user_avatar(&self, id: &ID, avatar: Option<Box<[u8]>>) -> Result<()>;
async fn get_user_avatar(&self, id: &ID) -> Result<Option<Box<[u8]>>>;
async fn update_user_name(&self, id: &ID, name: String) -> Result<()>;
}

fn validate_password(value: &str) -> Result<(), validator::ValidationError> {
Expand Down
37 changes: 37 additions & 0 deletions ee/tabby-schema/src/schema/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,41 @@ use regex::Regex;

lazy_static! {
pub static ref REPOSITORY_NAME_REGEX: Regex = Regex::new("^[a-zA-Z][\\w.-]+$").unwrap();
pub static ref USERNAME_REGEX: Regex =
Regex::new(r"^[^0-9±!@£$%^&*_+§¡€#¢¶•ªº«\\/<>?:;|=.,]{2,20}$").unwrap();
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_username_regex() {
let test_cases = vec![
("John", true), // English name
("Müller", true), // German name
("Jørgensen", true), // Danish name
("李雷", true), // Chinese name
("あきは", true), // Japanese name
("김민수", true), // Korean name
("Алексей", true), // Russian name
("José", true), // Spanish names
("علی", true), // Iranian names
// Edge cases
("", false), // Empty string
("JohnDoeIsAReallyLongName", false), // More than 20 characters
("John!", false), // Invalid character '!'
("José@", false), // Invalid character '@'
("12345", false), // Invalid character Numbers
("John_Doe", false), // Underscore character
("Anna-Marie", true), // Hyphen character
("O'Connor", true), // Apostrophe
("李@伟", false),
];

for (name, expected) in test_cases {
let result = USERNAME_REGEX.is_match(name);
assert_eq!(result, expected, "Failed for name: {}", name);
}
}
}
13 changes: 13 additions & 0 deletions ee/tabby-schema/src/schema/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,19 @@ impl Mutation {
Ok(true)
}

async fn update_user_name(ctx: &Context, id: ID, name: String) -> Result<bool> {
let claims = check_claims(ctx)?;
if claims.sub != id {
return Err(CoreError::Unauthorized(
"You cannot change another user's name",
));
}
let input = auth::UpdateUserNameInput { name };
input.validate()?;
ctx.locator.auth().update_user_name(&id, input.name).await?;
Ok(true)
}

async fn register(
ctx: &Context,
email: String,
Expand Down
9 changes: 9 additions & 0 deletions ee/tabby-webserver/src/service/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,15 @@ impl AuthenticationService for AuthenticationServiceImpl {
Ok(self.db.get_user_avatar(id.as_rowid()?).await?)
}

async fn update_user_name(&self, id: &ID, name: String) -> Result<()> {
if is_demo_mode() {
bail!("Changing profile data is disabled in demo mode");
}
let id = id.as_rowid()?;
self.db.update_user_name(id, name).await?;
Ok(())
}

async fn token_auth(&self, email: String, password: String) -> Result<TokenAuthResponse> {
let Some(user) = self.db.get_user_by_email(&email).await? else {
bail!("Invalid email address or password");
Expand Down

0 comments on commit b040d22

Please sign in to comment.