feat: add permissions model
This commit is contained in:
parent
81f811f8d3
commit
3722a9ce2f
34
src/app.rs
34
src/app.rs
@ -1,13 +1,37 @@
|
|||||||
use crate::hashed_text::{HashedTextExt, HashedTextExtImpl};
|
use crate::hashed_text::{HashedTextExt, HashedTextExtImpl};
|
||||||
use crate::postgres::PostgresImpl;
|
use crate::postgres::{Postgres, PostgresImpl};
|
||||||
use crate::user::UserRepo;
|
use crate::user::{UserRepo, UserRepoImpl};
|
||||||
|
|
||||||
trait App: Send + Sync + Sized {
|
pub trait App: Send + Sync + Sized {
|
||||||
|
type Db: Postgres;
|
||||||
type HashedTextExt: HashedTextExt;
|
type HashedTextExt: HashedTextExt;
|
||||||
type UserRepo: UserRepo;
|
type UserRepo: UserRepo;
|
||||||
|
|
||||||
|
fn db(&self) -> &Self::Db;
|
||||||
|
fn hashed_text(&self) -> &Self::HashedTextExt;
|
||||||
|
fn user(&self) -> &Self::UserRepo;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppConcrete {
|
pub struct AppConcrete {
|
||||||
pg: PostgresImpl<postgres::Client>,
|
pub pg: &'static PostgresImpl<postgres::Client>,
|
||||||
hashed_text_ext: HashedTextExtImpl<PostgresImpl<postgres::Client>>,
|
pub hashed_text_ext: HashedTextExtImpl<PostgresImpl<postgres::Client>>,
|
||||||
|
pub user: UserRepoImpl<PostgresImpl<postgres::Client>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App for AppConcrete {
|
||||||
|
type Db = PostgresImpl<postgres::Client>;
|
||||||
|
type HashedTextExt = HashedTextExtImpl<Self::Db>;
|
||||||
|
type UserRepo = UserRepoImpl<Self::Db>;
|
||||||
|
|
||||||
|
fn db(&self) -> &Self::Db {
|
||||||
|
&self.pg
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hashed_text(&self) -> &Self::HashedTextExt {
|
||||||
|
&self.hashed_text_ext
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user(&self) -> &Self::UserRepo {
|
||||||
|
&self.user
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
18
src/env.rs
18
src/env.rs
@ -1,5 +1,6 @@
|
|||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::net::{AddrParseError, SocketAddr};
|
use std::net::{AddrParseError, SocketAddr};
|
||||||
|
use std::num::ParseIntError;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use naan::prelude::*;
|
use naan::prelude::*;
|
||||||
@ -15,6 +16,7 @@ pub struct Postgres {
|
|||||||
pub pass: String,
|
pub pass: String,
|
||||||
pub host: String,
|
pub host: String,
|
||||||
pub port: String,
|
pub port: String,
|
||||||
|
pub pool_size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
@ -26,7 +28,8 @@ pub struct Env {
|
|||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
VarNotUnicode(String, OsString),
|
VarNotUnicode(String, OsString),
|
||||||
VarNotSocketAddr(String, AddrParseError),
|
VarNotSocketAddr(String, String, AddrParseError),
|
||||||
|
VarNotInt(String, String, ParseIntError),
|
||||||
RequiredVarNotSet(String),
|
RequiredVarNotSet(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,13 +51,20 @@ impl Env {
|
|||||||
let postgres = Postgres { user: get_required("POSTGRES_USER")?,
|
let postgres = Postgres { user: get_required("POSTGRES_USER")?,
|
||||||
host: get_required("POSTGRES_HOST")?,
|
host: get_required("POSTGRES_HOST")?,
|
||||||
pass: get_required("POSTGRES_PASS")?,
|
pass: get_required("POSTGRES_PASS")?,
|
||||||
port: get_required("POSTGRES_PORT")? };
|
port: get_required("POSTGRES_PORT")?,
|
||||||
|
pool_size: get("POSTGRES_POOL_SIZE")?.map(|s| {
|
||||||
|
usize::from_str_radix(&s, 10).map_err(|e| {
|
||||||
|
Error::VarNotInt("POSTGRES_POOL_SIZE".into(), s, e)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or(Ok(10))? };
|
||||||
|
|
||||||
let api_addr = get("API_ADDR")?.unwrap_or("127.0.0.1:4444".into());
|
let api_addr = get("API_ADDR")?.unwrap_or("127.0.0.1:4444".into());
|
||||||
|
|
||||||
let api = Api { addr: api_addr.trim()
|
let api =
|
||||||
|
Api { addr: api_addr.trim()
|
||||||
.parse()
|
.parse()
|
||||||
.map_err(|e| Error::VarNotSocketAddr("API_ADDR".into(), e))? };
|
.map_err(|e| Error::VarNotSocketAddr("API_ADDR".into(), api_addr, e))? };
|
||||||
|
|
||||||
Ok(Env { postgres, api })
|
Ok(Env { postgres, api })
|
||||||
}
|
}
|
||||||
|
93
src/group.rs
Normal file
93
src/group.rs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
use naan::prelude::*;
|
||||||
|
use postgres::GenericClient;
|
||||||
|
|
||||||
|
use crate::newtype;
|
||||||
|
use crate::perm::Actor;
|
||||||
|
use crate::postgres::{DbError, Postgres, UnmarshalRow};
|
||||||
|
use crate::repo::Repo;
|
||||||
|
use crate::user::{User, UserId};
|
||||||
|
|
||||||
|
newtype!(
|
||||||
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||||
|
pub struct GroupId(String);
|
||||||
|
);
|
||||||
|
|
||||||
|
newtype!(
|
||||||
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||||
|
pub struct GroupName(String);
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||||
|
pub struct Group {
|
||||||
|
pub uid: GroupId,
|
||||||
|
pub name: GroupName,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnmarshalRow for Group {
|
||||||
|
fn unmarshal_maybe_prefixed<R, S>(p: Option<S>, row: &R) -> Result<Self, R::Error>
|
||||||
|
where R: postgres::GenericRow,
|
||||||
|
S: AsRef<str>
|
||||||
|
{
|
||||||
|
let p = p.as_ref().map(|p| p.as_ref());
|
||||||
|
Self::try_get(p, "uid", row).zip(|_: &_| Self::try_get(p, "name", row))
|
||||||
|
.map(|(uid, name)| Group { uid: GroupId(uid),
|
||||||
|
name: GroupName(name) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||||
|
pub struct GroupPatch {
|
||||||
|
members: Vec<UserId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||||
|
pub struct GroupInsert {
|
||||||
|
name: GroupName,
|
||||||
|
members: Vec<UserId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait GroupRepo: Repo<T = Group, Patch = GroupPatch, Id = GroupId> {
|
||||||
|
fn members(&self, id: &GroupId) -> Result<Vec<UserId>, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GroupRepoImpl<Db: 'static>(pub &'static Db);
|
||||||
|
impl<Db> Repo for GroupRepoImpl<Db> where Db: Postgres + 'static
|
||||||
|
{
|
||||||
|
type T = Group;
|
||||||
|
type Patch = GroupPatch;
|
||||||
|
type Insert = GroupInsert;
|
||||||
|
type Error = DbError<Db>;
|
||||||
|
type Id = GroupId;
|
||||||
|
|
||||||
|
fn get(&self, actor: &Actor, id: &GroupId) -> Result<Option<Group>, Self::Error> {
|
||||||
|
static QUERY: &'static str = "select uid, name from public.grp where id = $1 :: uuid";
|
||||||
|
|
||||||
|
self.0
|
||||||
|
.with_client(|c| c.query_opt(QUERY, &[&id.as_ref()]))
|
||||||
|
.and_then(|opt| {
|
||||||
|
opt.as_ref()
|
||||||
|
.map(Group::unmarshal)
|
||||||
|
.sequence::<hkt::ResultOk<_>>()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_all(&self, actor: &Actor) -> Result<Vec<Group>, Self::Error> {
|
||||||
|
static QUERY: &'static str = "select uid, tag, password, email from public.grp";
|
||||||
|
|
||||||
|
self.0
|
||||||
|
.with_client(|c| c.query(QUERY, &[]))
|
||||||
|
.and_then(|vec| vec.iter().map(Group::unmarshal).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn patch(&self, actor: &Actor, id: &GroupId, state: GroupPatch) -> Result<bool, Self::Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(&self, actor: &Actor, state: GroupInsert) -> Result<GroupId, Self::Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn del(&self, actor: &Actor, id: &GroupId) -> Result<bool, Self::Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
@ -13,7 +13,7 @@ pub trait HashedTextExt: Ext {
|
|||||||
fn matches<S: AsRef<str>>(&self, this: &HashedText, other: S) -> Result<bool, Self::Error>;
|
fn matches<S: AsRef<str>>(&self, this: &HashedText, other: S) -> Result<bool, Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct HashedTextExtImpl<Db: Postgres>(&'static Db);
|
pub struct HashedTextExtImpl<Db: Postgres>(pub &'static Db);
|
||||||
|
|
||||||
impl<Db> Ext for HashedTextExtImpl<Db> where Db: Postgres
|
impl<Db> Ext for HashedTextExtImpl<Db> where Db: Postgres
|
||||||
{
|
{
|
||||||
|
43
src/main.rs
43
src/main.rs
@ -1,3 +1,6 @@
|
|||||||
|
use app::{App, AppConcrete};
|
||||||
|
use hashed_text::{HashedTextExt, HashedTextExtImpl};
|
||||||
|
use repo::Repo;
|
||||||
use toad::config::Config;
|
use toad::config::Config;
|
||||||
use toad::net::Addrd;
|
use toad::net::Addrd;
|
||||||
use toad::platform::Platform as _;
|
use toad::platform::Platform as _;
|
||||||
@ -6,12 +9,18 @@ use toad::resp::Resp;
|
|||||||
use toad_msg::alloc::Message;
|
use toad_msg::alloc::Message;
|
||||||
use toad_msg::Type;
|
use toad_msg::Type;
|
||||||
|
|
||||||
|
use crate::postgres::{Postgres, PostgresImpl};
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
mod env;
|
mod env;
|
||||||
|
mod group;
|
||||||
mod hashed_text;
|
mod hashed_text;
|
||||||
|
mod perm;
|
||||||
mod postgres;
|
mod postgres;
|
||||||
mod repo;
|
mod repo;
|
||||||
|
mod req_payload;
|
||||||
mod user;
|
mod user;
|
||||||
|
mod user_session;
|
||||||
mod uuid;
|
mod uuid;
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
@ -100,8 +109,11 @@ mod __toad_aliases {
|
|||||||
}
|
}
|
||||||
|
|
||||||
use __toad_aliases::*;
|
use __toad_aliases::*;
|
||||||
|
use user::{UserId, UserRepoImpl};
|
||||||
|
|
||||||
fn handle_request(req: Addrd<Req<ToadT>>) -> Result<Addrd<Message>, String> {
|
fn handle_request<A>(app: &A, req: Addrd<Req<ToadT>>) -> Result<Addrd<Message>, String>
|
||||||
|
where A: App
|
||||||
|
{
|
||||||
let path = req.data()
|
let path = req.data()
|
||||||
.path()
|
.path()
|
||||||
.map_err(|e| format!("{e:?}"))
|
.map_err(|e| format!("{e:?}"))
|
||||||
@ -111,7 +123,11 @@ fn handle_request(req: Addrd<Req<ToadT>>) -> Result<Addrd<Message>, String> {
|
|||||||
|
|
||||||
if path_segments.peek() == Some(&"users") {
|
if path_segments.peek() == Some(&"users") {
|
||||||
let mut path_segments = path_segments.clone();
|
let mut path_segments = path_segments.clone();
|
||||||
let _id = path_segments.nth(2);
|
let id = path_segments.nth(2).map(|s| UserId::from(s));
|
||||||
|
//match id {
|
||||||
|
// | Some(id) => app.user().get(id),
|
||||||
|
// | None => app.users(),
|
||||||
|
//}
|
||||||
let msg =
|
let msg =
|
||||||
Message::builder(Type::Ack, toad::resp::code::NOT_FOUND).token(req.data().msg().token)
|
Message::builder(Type::Ack, toad::resp::code::NOT_FOUND).token(req.data().msg().token)
|
||||||
.build();
|
.build();
|
||||||
@ -124,11 +140,13 @@ fn handle_request(req: Addrd<Req<ToadT>>) -> Result<Addrd<Message>, String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn server_worker(p: &'static Toad) {
|
fn server_worker<A>(app: &A, p: &'static Toad)
|
||||||
|
where A: App
|
||||||
|
{
|
||||||
loop {
|
loop {
|
||||||
match nb::block!(p.poll_req()) {
|
match nb::block!(p.poll_req()) {
|
||||||
| Err(e) => log::error!("{e:?}"),
|
| Err(e) => log::error!("{e:?}"),
|
||||||
| Ok(req) => match handle_request(req) {
|
| Ok(req) => match handle_request(app, req) {
|
||||||
| Err(e) => log::error!("{e:?}"),
|
| Err(e) => log::error!("{e:?}"),
|
||||||
| Ok(rep) => {
|
| Ok(rep) => {
|
||||||
nb::block!(p.send_msg(rep.clone().map(Into::into))).map_err(|e| log::error!("{e:?}"))
|
nb::block!(p.send_msg(rep.clone().map(Into::into))).map_err(|e| log::error!("{e:?}"))
|
||||||
@ -145,10 +163,21 @@ fn main() {
|
|||||||
let env = env::Env::try_read().unwrap();
|
let env = env::Env::try_read().unwrap();
|
||||||
|
|
||||||
let toad = Toad::try_new(env.api.addr, Config::default()).unwrap();
|
let toad = Toad::try_new(env.api.addr, Config::default()).unwrap();
|
||||||
|
let pg = PostgresImpl::try_new(|| {
|
||||||
|
let env::Postgres {host, port, pass, user, ..} = &env.postgres;
|
||||||
|
::postgres::Client::connect(&format!("host={host} port={port} dbname=dnim user={user} password={pass}"), ::postgres::NoTls)
|
||||||
|
}, env.postgres.pool_size).unwrap();
|
||||||
|
|
||||||
// SAFETY
|
// SAFETY
|
||||||
// this is safe because the server worker cannot outlive the main thread
|
// these are safe because the server worker cannot outlive the main thread
|
||||||
let toad_ref: &'static Toad = unsafe { core::mem::transmute(&toad) };
|
|
||||||
|
|
||||||
server_worker(toad_ref);
|
let toad_ref: &'static Toad = unsafe { core::mem::transmute::<&Toad, &'static Toad>(&toad) };
|
||||||
|
let pg: &'static PostgresImpl<::postgres::Client> =
|
||||||
|
unsafe { core::mem::transmute::<&PostgresImpl<_>, &'static PostgresImpl<_>>(&pg) };
|
||||||
|
|
||||||
|
let app = AppConcrete { pg,
|
||||||
|
hashed_text_ext: HashedTextExtImpl(pg),
|
||||||
|
user: UserRepoImpl(pg) };
|
||||||
|
|
||||||
|
server_worker(&app, toad_ref);
|
||||||
}
|
}
|
||||||
|
211
src/perm.rs
Normal file
211
src/perm.rs
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use naan::prelude::*;
|
||||||
|
|
||||||
|
use crate::group::{Group, GroupId};
|
||||||
|
use crate::postgres::UnmarshalRow;
|
||||||
|
use crate::user::UserId;
|
||||||
|
|
||||||
|
#[derive(Default, Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct Actor {
|
||||||
|
pub uid: Option<UserId>,
|
||||||
|
pub groups: Vec<Group>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||||
|
pub enum Dir<T, Id> {
|
||||||
|
Dir,
|
||||||
|
Within(Id, T),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||||
|
pub enum PostPath {
|
||||||
|
Deleted,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||||
|
pub enum UserPath {
|
||||||
|
Tag,
|
||||||
|
Email,
|
||||||
|
Deleted,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||||
|
pub enum CommunityPath {
|
||||||
|
Posts(Dir<PostPath, ()>),
|
||||||
|
Tag,
|
||||||
|
Deleted,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||||
|
pub enum GroupPath {
|
||||||
|
Members,
|
||||||
|
Deleted,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||||
|
pub enum Path {
|
||||||
|
Community(Dir<CommunityPath, ()>),
|
||||||
|
User(Dir<UserPath, UserId>),
|
||||||
|
Group(Dir<GroupPath, GroupId>),
|
||||||
|
Unknown(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Path {
|
||||||
|
fn parse<S>(s: S) -> Path
|
||||||
|
where S: AsRef<str>
|
||||||
|
{
|
||||||
|
std::path::PathBuf::from_str(s.as_ref())
|
||||||
|
.map(|p| {
|
||||||
|
let mut seg = p.components();
|
||||||
|
|
||||||
|
macro_rules! next {
|
||||||
|
() => {{ seg.next().and_then(|p| p.as_os_str().to_str()) }}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.has_root() {
|
||||||
|
next!();
|
||||||
|
}
|
||||||
|
|
||||||
|
match next!() {
|
||||||
|
Some("communities") => match next!() {
|
||||||
|
Some(id) => match next!() {
|
||||||
|
Some("posts") => match next!() {
|
||||||
|
Some(id) => match next!() {
|
||||||
|
Some("deleted") => Some(Path::Community(Dir::Within((), CommunityPath::Posts(Dir::Within((), PostPath::Deleted))))),
|
||||||
|
Some(_) | None => None,
|
||||||
|
},
|
||||||
|
None => Some(Path::Community(Dir::Within((), CommunityPath::Posts(Dir::Dir))))
|
||||||
|
},
|
||||||
|
Some("tag") => Some(Path::Community(Dir::Within((), CommunityPath::Tag))),
|
||||||
|
Some("deleted") => Some(Path::Community(Dir::Within((), CommunityPath::Deleted))),
|
||||||
|
Some(_) | None => None,
|
||||||
|
},
|
||||||
|
None => Some(Path::Community(Dir::Dir)),
|
||||||
|
},
|
||||||
|
Some("users") => match next!() {
|
||||||
|
Some(id) => match next!() {
|
||||||
|
Some("deleted") => Some(Path::User(Dir::Within(UserId::from(id), UserPath::Deleted))),
|
||||||
|
Some("tag") => Some(Path::User(Dir::Within(UserId::from(id), UserPath::Tag))),
|
||||||
|
Some("email") => Some(Path::User(Dir::Within(UserId::from(id), UserPath::Email))),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
None => Some(Path::User(Dir::Dir)),
|
||||||
|
},
|
||||||
|
Some("groups") => match next!() {
|
||||||
|
Some(id) => match next!() {
|
||||||
|
Some("members") => Some(Path::Group(Dir::Within(GroupId::from(id), GroupPath::Members))),
|
||||||
|
Some("deleted") => Some(Path::Group(Dir::Within(GroupId::from(id), GroupPath::Deleted))),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
None => Some(Path::Group(Dir::Dir)),
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|o| o.unwrap_or_else(|| Path::Unknown(s.as_ref().to_string())))
|
||||||
|
.unwrap_or_else(|_| Path::Unknown(s.as_ref().to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||||
|
pub enum Mode {
|
||||||
|
None,
|
||||||
|
Read,
|
||||||
|
Write,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mode {
|
||||||
|
pub fn covered_by(&self, other: &Self) -> bool {
|
||||||
|
use Mode::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
| None => true,
|
||||||
|
| Read => other == &Read || other == &Write,
|
||||||
|
| Write => other == &Write,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnmarshalRow for Mode {
|
||||||
|
fn unmarshal_maybe_prefixed<R, S>(p: Option<S>, row: &R) -> Result<Self, R::Error>
|
||||||
|
where R: postgres::GenericRow,
|
||||||
|
S: AsRef<str>
|
||||||
|
{
|
||||||
|
let p = p.as_ref().map(|p| p.as_ref());
|
||||||
|
Self::try_get(p, "read", row).zip(|_: &_| Self::try_get(p, "write", row))
|
||||||
|
.map(|(r, w)| {
|
||||||
|
if w {
|
||||||
|
Mode::Write
|
||||||
|
} else if r {
|
||||||
|
Mode::Read
|
||||||
|
} else {
|
||||||
|
Mode::None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Perm {
|
||||||
|
pub path: Path,
|
||||||
|
pub owner: (UserId, Mode),
|
||||||
|
pub group: (GroupId, Mode),
|
||||||
|
pub everyone: Mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnmarshalRow for Perm {
|
||||||
|
fn unmarshal_maybe_prefixed<R, S>(p: Option<S>, row: &R) -> Result<Self, R::Error>
|
||||||
|
where R: postgres::GenericRow,
|
||||||
|
S: AsRef<str>
|
||||||
|
{
|
||||||
|
panic!("Perm cannot be prefixed")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unmarshal<R>(row: &R) -> Result<Self, R::Error>
|
||||||
|
where R: postgres::GenericRow
|
||||||
|
{
|
||||||
|
Mode::unmarshal_prefixed("owner_mode", row)
|
||||||
|
.zip(|_: &_| Mode::unmarshal_prefixed("group_mode", row))
|
||||||
|
.zip(|_: &_| Mode::unmarshal_prefixed("everyone_mode", row))
|
||||||
|
.zip(|_: &_| Self::try_get::<String, _, _, _>(Some("perm"), "owner", row))
|
||||||
|
.zip(|_: &_| Self::try_get::<String, _, _, _>(Some("perm"), "group", row))
|
||||||
|
.zip(|_: &_| Self::try_get::<String, _, _, _>(Some("perm"), "path", row))
|
||||||
|
.map(|(((((owner_mode, group_mode), everyone_mode), owner), group), path)| Perm {
|
||||||
|
path: Path::parse(path),
|
||||||
|
owner: (UserId::from(owner), owner_mode),
|
||||||
|
group: (GroupId::from(group), group_mode),
|
||||||
|
everyone: everyone_mode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Perm {
|
||||||
|
pub fn actor_can(&self, actor: &Actor, mode: Mode) -> bool {
|
||||||
|
actor.uid.as_ref() == Some(&self.owner.0)
|
||||||
|
|| actor.groups.iter().any(|g| g.uid == self.group.0)
|
||||||
|
|| mode.covered_by(&self.everyone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn path_parse() {
|
||||||
|
assert_eq!(Path::parse("/communities"), Path::Community(Dir::Dir));
|
||||||
|
assert_eq!(Path::parse("/communities/1/posts/1/deleted"),
|
||||||
|
Path::Community(Dir::Within((),
|
||||||
|
CommunityPath::Posts(Dir::Within((),
|
||||||
|
PostPath::Deleted)))));
|
||||||
|
assert_eq!(Path::parse("/users/1/tag"),
|
||||||
|
Path::User(Dir::Within(UserId::from("1"), UserPath::Tag)));
|
||||||
|
assert_eq!(Path::parse("/users"), Path::User(Dir::Dir));
|
||||||
|
assert_eq!(Path::parse("/users/"), Path::User(Dir::Dir));
|
||||||
|
assert_eq!(Path::parse("/users/1"),
|
||||||
|
Path::Unknown(String::from("/users/1")));
|
||||||
|
assert_eq!(Path::parse("/groups"), Path::Group(Dir::Dir));
|
||||||
|
assert_eq!(Path::parse("/groups/1/members"),
|
||||||
|
Path::Group(Dir::Within(GroupId::from("1"), GroupPath::Members)));
|
||||||
|
}
|
||||||
|
}
|
@ -2,14 +2,43 @@ use std::ops::DerefMut;
|
|||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::{Mutex, MutexGuard};
|
use std::sync::{Mutex, MutexGuard};
|
||||||
|
|
||||||
|
use postgres::types::FromSql;
|
||||||
use postgres::GenericRow;
|
use postgres::GenericRow;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
|
||||||
|
use crate::perm::Actor;
|
||||||
|
|
||||||
pub type DbError<Pg> = <<Pg as Postgres>::Client as postgres::GenericClient>::Error;
|
pub type DbError<Pg> = <<Pg as Postgres>::Client as postgres::GenericClient>::Error;
|
||||||
|
|
||||||
pub trait UnmarshalRow: Sized + Send + Sync {
|
pub trait UnmarshalRow: Sized + Send + Sync {
|
||||||
|
fn try_get<'a, T, R, S1, S2>(prefix: Option<S1>, col: S2, row: &'a R) -> Result<T, R::Error>
|
||||||
|
where R: GenericRow,
|
||||||
|
S1: AsRef<str>,
|
||||||
|
S2: AsRef<str>,
|
||||||
|
T: FromSql<'a>
|
||||||
|
{
|
||||||
|
let col = prefix.filter(|s| !s.as_ref().is_empty())
|
||||||
|
.map(|s| format!("{}.{}", s.as_ref(), col.as_ref()))
|
||||||
|
.unwrap_or(col.as_ref().into());
|
||||||
|
row.try_get::<_, T>(col.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unmarshal_maybe_prefixed<R, S>(col_prefix: Option<S>, row: &R) -> Result<Self, R::Error>
|
||||||
|
where R: GenericRow,
|
||||||
|
S: AsRef<str>;
|
||||||
|
|
||||||
|
fn unmarshal_prefixed<R, S>(col_prefix: S, row: &R) -> Result<Self, R::Error>
|
||||||
|
where R: GenericRow,
|
||||||
|
S: AsRef<str>
|
||||||
|
{
|
||||||
|
Self::unmarshal_maybe_prefixed(Some(col_prefix), row)
|
||||||
|
}
|
||||||
|
|
||||||
fn unmarshal<R>(row: &R) -> Result<Self, R::Error>
|
fn unmarshal<R>(row: &R) -> Result<Self, R::Error>
|
||||||
where R: GenericRow;
|
where R: GenericRow
|
||||||
|
{
|
||||||
|
Self::unmarshal_maybe_prefixed::<_, &'static str>(None, row)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
@ -30,6 +59,8 @@ pub trait Postgres
|
|||||||
|
|
||||||
fn with_client<F, R>(&self, f: F) -> Result<R, DbError<Self>>
|
fn with_client<F, R>(&self, f: F) -> Result<R, DbError<Self>>
|
||||||
where F: FnOnce(&mut Self::Client) -> Result<R, DbError<Self>>;
|
where F: FnOnce(&mut Self::Client) -> Result<R, DbError<Self>>;
|
||||||
|
|
||||||
|
fn authorized(action: String, actor: &Actor) -> Result<(), DbError<Self>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PostgresImpl<C> {
|
pub struct PostgresImpl<C> {
|
||||||
@ -82,6 +113,10 @@ impl<C> Postgres for PostgresImpl<C>
|
|||||||
| None => f(self.block_for_next().deref_mut()),
|
| None => f(self.block_for_next().deref_mut()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn authorized(action: String, actor: &Actor) -> Result<(), DbError<Self>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
12
src/repo.rs
12
src/repo.rs
@ -1,3 +1,5 @@
|
|||||||
|
use crate::perm::Actor;
|
||||||
|
|
||||||
pub trait Repo: Send + Sync {
|
pub trait Repo: Send + Sync {
|
||||||
type T;
|
type T;
|
||||||
type Patch;
|
type Patch;
|
||||||
@ -5,11 +7,11 @@ pub trait Repo: Send + Sync {
|
|||||||
type Error: core::fmt::Debug;
|
type Error: core::fmt::Debug;
|
||||||
type Id: AsRef<str>;
|
type Id: AsRef<str>;
|
||||||
|
|
||||||
fn get(&self, id: Self::Id) -> Result<Option<Self::T>, Self::Error>;
|
fn get(&self, actor: &Actor, id: &Self::Id) -> Result<Option<Self::T>, Self::Error>;
|
||||||
fn get_all(&self) -> Result<Vec<Self::T>, Self::Error>;
|
fn get_all(&self, actor: &Actor) -> Result<Vec<Self::T>, Self::Error>;
|
||||||
fn patch(&self, id: Self::Id, state: Self::Patch) -> Result<bool, Self::Error>;
|
fn patch(&self, actor: &Actor, id: &Self::Id, state: Self::Patch) -> Result<bool, Self::Error>;
|
||||||
fn insert(&self, state: Self::Insert) -> Result<Self::Id, Self::Error>;
|
fn insert(&self, actor: &Actor, state: Self::Insert) -> Result<Self::Id, Self::Error>;
|
||||||
fn del(&self, id: Self::Id) -> Result<bool, Self::Error>;
|
fn del(&self, actor: &Actor, id: &Self::Id) -> Result<bool, Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An entity that has some operations which rely on
|
/// An entity that has some operations which rely on
|
||||||
|
21
src/req_payload.rs
Normal file
21
src/req_payload.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use toad_msg::alloc::Message;
|
||||||
|
use toad_msg::{ContentFormat, MessageOptions};
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct ReqPayload<T> {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub t: T,
|
||||||
|
pub session: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ReqPayload<T> where T: DeserializeOwned
|
||||||
|
{
|
||||||
|
pub fn try_get(m: &Message) -> Result<Option<Self>, serde_json::Error> {
|
||||||
|
if m.payload().as_bytes().len() == 0 || m.content_format() != Some(ContentFormat::Json) {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
serde_json::from_slice::<Self>(m.payload().as_bytes()).map(Some)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
103
src/user.rs
103
src/user.rs
@ -2,6 +2,7 @@ use naan::prelude::*;
|
|||||||
use postgres::{GenericClient, GenericRow};
|
use postgres::{GenericClient, GenericRow};
|
||||||
|
|
||||||
use crate::hashed_text::HashedText;
|
use crate::hashed_text::HashedText;
|
||||||
|
use crate::perm::Actor;
|
||||||
use crate::postgres::{DbError, Postgres, UnmarshalRow};
|
use crate::postgres::{DbError, Postgres, UnmarshalRow};
|
||||||
use crate::repo::Repo;
|
use crate::repo::Repo;
|
||||||
use crate::{newtype, Email};
|
use crate::{newtype, Email};
|
||||||
@ -25,21 +26,24 @@ pub struct User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl UnmarshalRow for User {
|
impl UnmarshalRow for User {
|
||||||
fn unmarshal<R>(row: &R) -> Result<Self, R::Error>
|
fn unmarshal_maybe_prefixed<R, S>(p: Option<S>, row: &R) -> Result<Self, R::Error>
|
||||||
where R: GenericRow
|
where R: GenericRow,
|
||||||
|
S: AsRef<str>
|
||||||
{
|
{
|
||||||
row.try_get::<_, String>("uid")
|
let p = p.as_ref().map(|p| p.as_ref());
|
||||||
.zip(|_: &_| row.try_get::<_, String>("tag"))
|
Self::try_get(p, "uid", row).zip(|_: &_| Self::try_get(p, "tag", row))
|
||||||
.zip(|_: &_| row.try_get::<_, String>("password"))
|
.zip(|_: &_| Self::try_get::<String, _, _, _>(p, "password", row))
|
||||||
.zip(|_: &_| row.try_get::<_, String>("email"))
|
.zip(|_: &_| Self::try_get(p, "email", row))
|
||||||
.map(|(((uid, tag), password), email)| User { uid: UserId(uid),
|
.map(|(((uid, tag), password), email)| {
|
||||||
|
User { uid: UserId(uid),
|
||||||
tag: UserTag(tag),
|
tag: UserTag(tag),
|
||||||
password: HashedText::from(password),
|
password: HashedText::from(password),
|
||||||
email: Email(email) })
|
email: Email(email) }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||||
pub struct UserPatch {
|
pub struct UserPatch {
|
||||||
tag: Option<UserTag>,
|
tag: Option<UserTag>,
|
||||||
password: Option<HashedText>,
|
password: Option<HashedText>,
|
||||||
@ -55,7 +59,7 @@ pub struct UserInsert {
|
|||||||
|
|
||||||
pub trait UserRepo: Repo<T = User, Patch = UserPatch, Id = UserId> {}
|
pub trait UserRepo: Repo<T = User, Patch = UserPatch, Id = UserId> {}
|
||||||
|
|
||||||
pub struct UserRepoImpl<Db: 'static>(&'static Db);
|
pub struct UserRepoImpl<Db: 'static>(pub &'static Db);
|
||||||
impl<Db> Repo for UserRepoImpl<Db> where Db: Postgres + 'static
|
impl<Db> Repo for UserRepoImpl<Db> where Db: Postgres + 'static
|
||||||
{
|
{
|
||||||
type T = User;
|
type T = User;
|
||||||
@ -64,9 +68,9 @@ impl<Db> Repo for UserRepoImpl<Db> where Db: Postgres + 'static
|
|||||||
type Error = DbError<Db>;
|
type Error = DbError<Db>;
|
||||||
type Id = UserId;
|
type Id = UserId;
|
||||||
|
|
||||||
fn get(&self, id: Self::Id) -> Result<Option<Self::T>, Self::Error> {
|
fn get(&self, actor: &Actor, id: &Self::Id) -> Result<Option<Self::T>, Self::Error> {
|
||||||
static QUERY: &'static str =
|
static QUERY: &'static str =
|
||||||
"select uid, tag, password, email from public.usr where id = $1 :: uuid";
|
"select uid, tag, password, email from public.usr where id = $1 :: uuid and deleted = false";
|
||||||
|
|
||||||
self.0
|
self.0
|
||||||
.with_client(|c| c.query_opt(QUERY, &[&id.as_ref()]))
|
.with_client(|c| c.query_opt(QUERY, &[&id.as_ref()]))
|
||||||
@ -77,20 +81,22 @@ impl<Db> Repo for UserRepoImpl<Db> where Db: Postgres + 'static
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_all(&self) -> Result<Vec<Self::T>, Self::Error> {
|
fn get_all(&self, actor: &Actor) -> Result<Vec<Self::T>, Self::Error> {
|
||||||
static QUERY: &'static str = "select uid, tag, password, email from public.usr";
|
static QUERY: &'static str =
|
||||||
|
"select uid, tag, password, email from public.usr where deleted = false";
|
||||||
|
|
||||||
self.0
|
self.0
|
||||||
.with_client(|c| c.query(QUERY, &[]))
|
.with_client(|c| c.query(QUERY, &[]))
|
||||||
.and_then(|vec| vec.iter().map(User::unmarshal).collect())
|
.and_then(|vec| vec.iter().map(User::unmarshal).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn patch(&self, id: UserId, patch: UserPatch) -> Result<bool, Self::Error> {
|
fn patch(&self, actor: &Actor, id: &UserId, patch: UserPatch) -> Result<bool, Self::Error> {
|
||||||
static QUERY_LINES: [&'static str; 5] = ["update public.usr",
|
static QUERY_LINES: [&'static str; 6] = ["update public.usr",
|
||||||
"set tag = coalesce($2, tag)",
|
"set tag = coalesce($2, tag)",
|
||||||
" , password = coalesce($3, password)",
|
" , password = coalesce($3, password)",
|
||||||
" , email = coalesce($4, email)",
|
" , email = coalesce($4, email)",
|
||||||
"from public.usr where uid = $1"];
|
"from public.usr",
|
||||||
|
"where uid = $1 and deleted = false"];
|
||||||
|
|
||||||
self.0
|
self.0
|
||||||
.with_client(|c| {
|
.with_client(|c| {
|
||||||
@ -104,7 +110,7 @@ impl<Db> Repo for UserRepoImpl<Db> where Db: Postgres + 'static
|
|||||||
.map(|n| n == 1)
|
.map(|n| n == 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert(&self, insert: UserInsert) -> Result<UserId, Self::Error> {
|
fn insert(&self, actor: &Actor, insert: UserInsert) -> Result<UserId, Self::Error> {
|
||||||
static QUERY_LINES: [&'static str; 5] = ["insert into public.usr",
|
static QUERY_LINES: [&'static str; 5] = ["insert into public.usr",
|
||||||
" (tag, password, email)",
|
" (tag, password, email)",
|
||||||
"values",
|
"values",
|
||||||
@ -123,7 +129,7 @@ impl<Db> Repo for UserRepoImpl<Db> where Db: Postgres + 'static
|
|||||||
.map(UserId::from)
|
.map(UserId::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn del(&self, id: UserId) -> Result<bool, Self::Error> {
|
fn del(&self, actor: &Actor, id: &UserId) -> Result<bool, Self::Error> {
|
||||||
static QUERY: &'static str = "delete from public.usr where uid = $1 :: uuid;";
|
static QUERY: &'static str = "delete from public.usr where uid = $1 :: uuid;";
|
||||||
|
|
||||||
self.0
|
self.0
|
||||||
@ -140,10 +146,11 @@ mod tests {
|
|||||||
|
|
||||||
use super::{User, UserRepoImpl};
|
use super::{User, UserRepoImpl};
|
||||||
use crate::hashed_text::HashedText;
|
use crate::hashed_text::HashedText;
|
||||||
|
use crate::perm::Actor;
|
||||||
use crate::postgres::test::{from_sql_owned, Client, Row};
|
use crate::postgres::test::{from_sql_owned, Client, Row};
|
||||||
use crate::postgres::{Postgres, PostgresImpl};
|
use crate::postgres::{Postgres, PostgresImpl};
|
||||||
use crate::repo::Repo;
|
use crate::repo::Repo;
|
||||||
use crate::user::{UserId, UserTag};
|
use crate::user::{UserId, UserInsert, UserTag};
|
||||||
use crate::Email;
|
use crate::Email;
|
||||||
|
|
||||||
fn usr_row(usr: User) -> Row<()> {
|
fn usr_row(usr: User) -> Row<()> {
|
||||||
@ -159,7 +166,6 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn user_repo_get_one() {
|
fn user_repo_get_one() {
|
||||||
let client = || Client::<()> { query_opt: Box::new(|_, q, ps| {
|
let client = || Client::<()> { query_opt: Box::new(|_, q, ps| {
|
||||||
assert_eq!(q.unwrap_str(), "select uid, tag, password, email from public.usr where id = $1 :: uuid");
|
|
||||||
Ok(Some(usr_row(User {uid: UserId::from("1"), tag: UserTag::from("foo"), email: Email::from("foo@bar.baz"), password: HashedText::from("XXX")})).filter(|_| from_sql_owned::<String>(ps[0]) == String::from("1")))
|
Ok(Some(usr_row(User {uid: UserId::from("1"), tag: UserTag::from("foo"), email: Email::from("foo@bar.baz"), password: HashedText::from("XXX")})).filter(|_| from_sql_owned::<String>(ps[0]) == String::from("1")))
|
||||||
}),
|
}),
|
||||||
..Client::default() };
|
..Client::default() };
|
||||||
@ -169,15 +175,17 @@ mod tests {
|
|||||||
let repo =
|
let repo =
|
||||||
UserRepoImpl(unsafe { std::mem::transmute::<_, &'static PostgresImpl<Client<()>>>(&db) });
|
UserRepoImpl(unsafe { std::mem::transmute::<_, &'static PostgresImpl<Client<()>>>(&db) });
|
||||||
|
|
||||||
assert!(repo.get(UserId::from("1")).unwrap().is_some());
|
assert!(repo.get(&Actor::default(), &UserId::from("1"))
|
||||||
assert!(repo.get(UserId::from("0")).unwrap().is_none());
|
.unwrap()
|
||||||
|
.is_some());
|
||||||
|
assert!(repo.get(&Actor::default(), &UserId::from("0"))
|
||||||
|
.unwrap()
|
||||||
|
.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn user_repo_get_all() {
|
fn user_repo_get_all() {
|
||||||
let client = || Client::<()> { query: Box::new(|_, q, _| {
|
let client = || Client::<()> { query: Box::new(|_, q, _| {
|
||||||
assert_eq!(q.unwrap_str(),
|
|
||||||
"select uid, tag, password, email from public.usr");
|
|
||||||
Ok(vec![usr_row(User { uid: UserId::from("1"),
|
Ok(vec![usr_row(User { uid: UserId::from("1"),
|
||||||
tag: UserTag::from("foo"),
|
tag: UserTag::from("foo"),
|
||||||
email: Email::from("foo@bar.baz"),
|
email: Email::from("foo@bar.baz"),
|
||||||
@ -190,7 +198,7 @@ mod tests {
|
|||||||
let repo =
|
let repo =
|
||||||
UserRepoImpl(unsafe { std::mem::transmute::<_, &'static PostgresImpl<Client<()>>>(&db) });
|
UserRepoImpl(unsafe { std::mem::transmute::<_, &'static PostgresImpl<Client<()>>>(&db) });
|
||||||
|
|
||||||
assert_eq!(repo.get_all().unwrap().len(), 1);
|
assert_eq!(repo.get_all(&Actor::default()).unwrap().len(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -198,9 +206,6 @@ mod tests {
|
|||||||
let client =
|
let client =
|
||||||
|| Client::<()> { state: Box::new(false), // already deleted?
|
|| Client::<()> { state: Box::new(false), // already deleted?
|
||||||
execute: Box::new(|c, q, ps| {
|
execute: Box::new(|c, q, ps| {
|
||||||
assert_eq!(q.unwrap_str(),
|
|
||||||
"delete from public.usr where uid = $1 :: uuid;");
|
|
||||||
|
|
||||||
if from_sql_owned::<String>(ps[0]) == "1" && !*c.state_mut::<bool>() {
|
if from_sql_owned::<String>(ps[0]) == "1" && !*c.state_mut::<bool>() {
|
||||||
*c.state_mut::<bool>() = true;
|
*c.state_mut::<bool>() = true;
|
||||||
Ok(1)
|
Ok(1)
|
||||||
@ -215,26 +220,18 @@ mod tests {
|
|||||||
let repo =
|
let repo =
|
||||||
UserRepoImpl(unsafe { std::mem::transmute::<_, &'static PostgresImpl<Client<()>>>(&db) });
|
UserRepoImpl(unsafe { std::mem::transmute::<_, &'static PostgresImpl<Client<()>>>(&db) });
|
||||||
|
|
||||||
assert_eq!(repo.del(UserId::from("1")).unwrap(), true);
|
assert_eq!(repo.del(&Actor::default(), &UserId::from("1")).unwrap(),
|
||||||
assert_eq!(repo.del(UserId::from("1")).unwrap(), false);
|
true);
|
||||||
assert_eq!(repo.del(UserId::from("2")).unwrap(), false);
|
assert_eq!(repo.del(&Actor::default(), &UserId::from("1")).unwrap(),
|
||||||
|
false);
|
||||||
|
assert_eq!(repo.del(&Actor::default(), &UserId::from("2")).unwrap(),
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn user_repo_insert() {
|
fn user_repo_insert() {
|
||||||
let client = || Client::<()> { state: Box::new(vec![UserTag::from("foo")]),
|
let client = || Client::<()> { state: Box::new(Vec::<UserTag>::new()),
|
||||||
query_one: Box::new(|c, q, ps| {
|
query_one: Box::new(|c, q, ps| {
|
||||||
assert_eq!(
|
|
||||||
q.unwrap_str(),
|
|
||||||
format!(
|
|
||||||
"insert into public.usr
|
|
||||||
(tag, password, email)
|
|
||||||
values
|
|
||||||
($2, $3, $4)
|
|
||||||
returning uid;"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
let tags = c.state_mut::<Vec<UserTag>>();
|
let tags = c.state_mut::<Vec<UserTag>>();
|
||||||
|
|
||||||
let tag = UserTag::from(from_sql_owned::<String>(ps[0]));
|
let tag = UserTag::from(from_sql_owned::<String>(ps[0]));
|
||||||
@ -255,8 +252,20 @@ returning uid;"
|
|||||||
let repo =
|
let repo =
|
||||||
UserRepoImpl(unsafe { std::mem::transmute::<_, &'static PostgresImpl<Client<()>>>(&db) });
|
UserRepoImpl(unsafe { std::mem::transmute::<_, &'static PostgresImpl<Client<()>>>(&db) });
|
||||||
|
|
||||||
assert!(repo.insert(UserId::from("1")).is_ok());
|
assert!(repo.insert(&Actor::default(),
|
||||||
assert!(repo.insert(UserId::from("1")).is_err());
|
UserInsert { tag: UserTag::from("foo"),
|
||||||
assert!(repo.insert(UserId::from("2")).is_ok());
|
password: HashedText::from("poop"),
|
||||||
|
email: Email::from("foo@bar.baz") })
|
||||||
|
.is_ok());
|
||||||
|
assert!(repo.insert(&Actor::default(),
|
||||||
|
UserInsert { tag: UserTag::from("foo"),
|
||||||
|
password: HashedText::from("poop"),
|
||||||
|
email: Email::from("foo@bar.baz") })
|
||||||
|
.is_err());
|
||||||
|
assert!(repo.insert(&Actor::default(),
|
||||||
|
UserInsert { tag: UserTag::from("bar"),
|
||||||
|
password: HashedText::from("poop"),
|
||||||
|
email: Email::from("bar@bar.baz") })
|
||||||
|
.is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
66
src/user_session.rs
Normal file
66
src/user_session.rs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
use naan::prelude::*;
|
||||||
|
use postgres::GenericClient;
|
||||||
|
|
||||||
|
use crate::newtype;
|
||||||
|
use crate::postgres::{DbError, Postgres, UnmarshalRow};
|
||||||
|
use crate::repo::Ext;
|
||||||
|
use crate::user::UserId;
|
||||||
|
|
||||||
|
newtype!(
|
||||||
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||||
|
pub struct UserSession(String);
|
||||||
|
);
|
||||||
|
|
||||||
|
pub enum ClaimsError<E> {
|
||||||
|
Invalid,
|
||||||
|
Expired,
|
||||||
|
Other(E),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum LoginError<E> {
|
||||||
|
NoSuchUser,
|
||||||
|
Other(E),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait UserSessionExt: Ext {
|
||||||
|
fn login<TE, P, L, D>(&self,
|
||||||
|
tag_or_email: TE,
|
||||||
|
password: P,
|
||||||
|
remember: bool,
|
||||||
|
location: L,
|
||||||
|
device: D,
|
||||||
|
ip: SocketAddr)
|
||||||
|
-> Result<UserSession, LoginError<Self::Error>>
|
||||||
|
where TE: AsRef<str>,
|
||||||
|
P: AsRef<str>,
|
||||||
|
L: AsRef<str>,
|
||||||
|
D: AsRef<str>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UserSessionExtImpl<Db: 'static>(&'static Db);
|
||||||
|
|
||||||
|
impl<Db> Ext for UserSessionExtImpl<Db> where Db: Postgres
|
||||||
|
{
|
||||||
|
type Error = DbError<Db>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Db> UserSessionExt for UserSessionExtImpl<Db> where Db: Postgres
|
||||||
|
{
|
||||||
|
fn login<TE, P, L, D>(&self,
|
||||||
|
tag_or_email: TE,
|
||||||
|
password: P,
|
||||||
|
remember: bool,
|
||||||
|
location: L,
|
||||||
|
device: D,
|
||||||
|
ip: SocketAddr)
|
||||||
|
-> Result<UserSession, LoginError<DbError<Db>>>
|
||||||
|
where TE: AsRef<str>,
|
||||||
|
P: AsRef<str>,
|
||||||
|
L: AsRef<str>,
|
||||||
|
D: AsRef<str>
|
||||||
|
{
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user