Committ
This commit is contained in:
commit
94502b0b54
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
7
.idea/inspectionProfiles/Project_Default.xml
Normal file
7
.idea/inspectionProfiles/Project_Default.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="SqlDialectInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="SqlNoDataSourceInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/user-lib.iml" filepath="$PROJECT_DIR$/.idea/user-lib.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
11
.idea/user-lib.iml
Normal file
11
.idea/user-lib.iml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="EMPTY_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
1846
Cargo.lock
generated
Normal file
1846
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
Cargo.toml
Normal file
20
Cargo.toml
Normal file
@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "user-lib"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Franklin E. Blanco"]
|
||||
description = "A library to add secure user authentication to any service."
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/franklinblanco/user-lib.git"
|
||||
|
||||
[lib]
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
sqlx = { version = "0.7", features = [ "runtime-tokio", "tls-rustls", "postgres", "chrono" ] }
|
||||
chrono = { version = "0.4", features = [ "serde" ] }
|
||||
ring = "0.16.20"
|
||||
data-encoding = "2.3.2"
|
||||
futures-util = "0.3"
|
||||
uuid = { version = "1.3.0", features = ["v4", "fast-rng", "macro-diagnostics", "serde"] }
|
14
Readme.md
Normal file
14
Readme.md
Normal file
@ -0,0 +1,14 @@
|
||||
# User-lib
|
||||
by Franklin Blanco
|
||||
|
||||
This library is my attempt at developing a recyclable utility for different projects, and not having to setup an authentication microservice each time I start a new project.
|
||||
|
||||
### Must use Postgres!
|
||||
|
||||
## How to use?
|
||||
Setup:
|
||||
- Add this library to your Cargo.toml
|
||||
- Copy the migrations from the migrations folder inside this library into your migrations
|
||||
- Add the user_lib::setup() function to your main. Make sure to pass it a PgPool
|
||||
- Add the user_lib::routes to your actix_web server (register, authenticate, change_password, refresh_token)
|
||||
Usage:
|
8
migrations/1_user.sql
Normal file
8
migrations/1_user.sql
Normal file
@ -0,0 +1,8 @@
|
||||
CREATE TABLE IF NOT EXISTS user (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
salt TEXT NOT NULL,
|
||||
time_created TIMESTAMPTZ NOT NULL,
|
||||
last_updated TIMESTAMPTZ NOT NULL,
|
||||
)
|
8
migrations/2_token.sql
Normal file
8
migrations/2_token.sql
Normal file
@ -0,0 +1,8 @@
|
||||
CREATE TABLE IF NOT EXISTS token (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
auth_token TEXT NOT NULL,
|
||||
refresh_token TEXT NOT NULL,
|
||||
time_created TIMESTAMPTZ NOT NULL,
|
||||
last_updated TIMESTAMPTZ NOT NULL
|
||||
)
|
8
migrations/3_credential.sql
Normal file
8
migrations/3_credential.sql
Normal file
@ -0,0 +1,8 @@
|
||||
CREATE TABLE IF NOT EXISTS credential (
|
||||
user_id INT NOT NULL,
|
||||
credential_type VARCHAR NOT NULL,
|
||||
credential VARCHAR NOT NULL,
|
||||
time_created TIMESTAMPTZ NOT NULL,
|
||||
last_updated TIMESTAMPTZ NOT NULL,
|
||||
PRIMARY KEY(user_id, credential_type)
|
||||
);
|
4
sql/schema/token/delete_expired_tokens.sql
Normal file
4
sql/schema/token/delete_expired_tokens.sql
Normal file
@ -0,0 +1,4 @@
|
||||
DELETE FROM token
|
||||
where
|
||||
TIMESTAMPDIFF(DAY, NOW(), last_updated) > ? AND
|
||||
TIMESTAMPDIFF(DAY, NOW(), time_created) > ?
|
3
sql/schema/token/find_with_user_id.sql
Normal file
3
sql/schema/token/find_with_user_id.sql
Normal file
@ -0,0 +1,3 @@
|
||||
SELECT *
|
||||
FROM token
|
||||
WHERE user_id = ?
|
3
sql/schema/token/insert.sql
Normal file
3
sql/schema/token/insert.sql
Normal file
@ -0,0 +1,3 @@
|
||||
INSERT INTO token
|
||||
(id, user_id, time_created, last_updated, auth_token, refresh_token)
|
||||
values (NULL, ?, NOW(), NOW(), ?, ?)
|
3
sql/schema/token/update.sql
Normal file
3
sql/schema/token/update.sql
Normal file
@ -0,0 +1,3 @@
|
||||
UPDATE token
|
||||
SET last_updated = NOW(), auth_token = ?, refresh_token = ?
|
||||
WHERE id = ?
|
5
sql/schema/user/find_with_credential.sql
Normal file
5
sql/schema/user/find_with_credential.sql
Normal file
@ -0,0 +1,5 @@
|
||||
SELECT *
|
||||
FROM user
|
||||
WHERE user.credential = ? AND
|
||||
user.credential_type = ? AND
|
||||
user.app = ?
|
3
sql/schema/user/find_with_id.sql
Normal file
3
sql/schema/user/find_with_id.sql
Normal file
@ -0,0 +1,3 @@
|
||||
SELECT *
|
||||
FROM user
|
||||
WHERE user.id = ?
|
3
sql/schema/user/insert.sql
Normal file
3
sql/schema/user/insert.sql
Normal file
@ -0,0 +1,3 @@
|
||||
INSERT INTO user
|
||||
(id, time_created, last_updated, app, credential, credential_type, name, password, salt) values
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
0
sql/schema/user/update.sql
Normal file
0
sql/schema/user/update.sql
Normal file
1
src/dao/mod.rs
Normal file
1
src/dao/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod pg_queries;
|
0
src/dao/pg_queries.rs
Normal file
0
src/dao/pg_queries.rs
Normal file
24
src/domain/credential.rs
Normal file
24
src/domain/credential.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
|
||||
/// Is used in the user struct to signal what type of credential will be used in the credential Column.
|
||||
/// Defaults to email.
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum CredentialType {
|
||||
PhoneNumber,
|
||||
#[default]
|
||||
Email,
|
||||
Username,
|
||||
}
|
||||
|
||||
/// Can only have one per user per cred_type
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Credential {
|
||||
pub user_id: i32,
|
||||
pub credential_type: CredentialType,
|
||||
pub credential: String,
|
||||
pub time_created: DateTime<Utc>,
|
||||
pub last_updated: DateTime<Utc>,
|
||||
}
|
14
src/domain/error.rs
Normal file
14
src/domain/error.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
|
||||
/// Used to return a simple error from FromStr implementations
|
||||
#[derive(Debug)]
|
||||
pub struct FromStrError;
|
||||
|
||||
impl Display for FromStrError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Error parsing string into value. FromStrError.")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for FromStrError {}
|
58
src/domain/impls/credential.rs
Normal file
58
src/domain/impls/credential.rs
Normal file
@ -0,0 +1,58 @@
|
||||
use std::{str::FromStr, fmt::Display};
|
||||
|
||||
use sqlx::{Postgres, postgres::{PgValueRef, PgArgumentBuffer, PgTypeInfo}, error::BoxDynError, encode::IsNull};
|
||||
|
||||
use crate::domain::{credential::CredentialType, error::FromStrError};
|
||||
|
||||
impl FromStr for CredentialType {
|
||||
type Err = FromStrError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"PhoneNumber" => Ok(Self::PhoneNumber),
|
||||
"Email" => Ok(Self::Email),
|
||||
"Username" => Ok(Self::Username),
|
||||
_ => Err(FromStrError)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Display for CredentialType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
CredentialType::PhoneNumber => write!(f, "PhoneNumber"),
|
||||
CredentialType::Email => write!(f, "Email"),
|
||||
CredentialType::Username => write!(f, "Username"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Sqlx implementations so that the CredentialType enum can be inserted & retrieved from the database
|
||||
//
|
||||
|
||||
impl sqlx::Encode<'_, Postgres> for CredentialType {
|
||||
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
|
||||
let binding = self.to_string();
|
||||
<&str as sqlx::Encode<Postgres>>::encode(&binding, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl sqlx::Decode<'_, Postgres> for CredentialType {
|
||||
fn decode(value: PgValueRef<'_>) -> Result<Self, BoxDynError> {
|
||||
let column = value.as_str()?;
|
||||
match Self::from_str(column) {
|
||||
Ok(listing_state) => Ok(listing_state),
|
||||
Err(error) => Err(Box::new(error)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl sqlx::Type<Postgres> for CredentialType {
|
||||
fn type_info() -> PgTypeInfo {
|
||||
PgTypeInfo::with_name("VARCHAR")
|
||||
}
|
||||
|
||||
fn compatible(ty: &<Postgres as sqlx::Database>::TypeInfo) -> bool {
|
||||
*ty == Self::type_info()
|
||||
}
|
||||
}
|
1
src/domain/impls/mod.rs
Normal file
1
src/domain/impls/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod credential;
|
5
src/domain/mod.rs
Normal file
5
src/domain/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub mod credential;
|
||||
pub mod token;
|
||||
pub mod user;
|
||||
pub mod impls;
|
||||
pub mod error;
|
21
src/domain/token.rs
Normal file
21
src/domain/token.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use sqlx::FromRow;
|
||||
|
||||
|
||||
#[derive(FromRow)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Token {
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub id: i32,
|
||||
#[serde(rename = "userId")]
|
||||
pub user_id: i32,
|
||||
#[serde(rename = "authToken")]
|
||||
pub auth_token: String,
|
||||
#[serde(rename = "refreshToken")]
|
||||
pub refresh_token: String,
|
||||
#[serde(rename = "timeCreated")]
|
||||
pub time_created: DateTime<Utc>,
|
||||
#[serde(rename = "lastUpdated")]
|
||||
pub last_updated: DateTime<Utc>,
|
||||
}
|
20
src/domain/user.rs
Normal file
20
src/domain/user.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use sqlx::FromRow;
|
||||
|
||||
use super::credential::CredentialType;
|
||||
|
||||
#[derive(FromRow)]
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct User {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub password: String,
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub salt: String,
|
||||
#[serde(rename = "timeCreated")]
|
||||
pub time_created: DateTime<Utc>,
|
||||
#[serde(rename = "lastUpdated")]
|
||||
pub last_updated: DateTime<Utc>,
|
||||
}
|
1
src/dto/mod.rs
Normal file
1
src/dto/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod users;
|
14
src/dto/users.rs
Normal file
14
src/dto/users.rs
Normal file
@ -0,0 +1,14 @@
|
||||
pub struct UsernameUserLoginPayload {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
pub struct EmailUserLoginPayload {
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
pub struct PhoneNumberUserLoginPayload {
|
||||
pub phone_number: String,
|
||||
pub password: String,
|
||||
}
|
5
src/lib.rs
Normal file
5
src/lib.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub mod dao;
|
||||
pub mod service;
|
||||
pub mod utils;
|
||||
pub mod domain;
|
||||
pub mod dto;
|
0
src/service/mod.rs
Normal file
0
src/service/mod.rs
Normal file
0
src/utils/mod.rs
Normal file
0
src/utils/mod.rs
Normal file
Loading…
Reference in New Issue
Block a user