Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/rpc/import/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ The RPC API provides a [JSON-RPC 2.0](https://www.jsonrpc.org/specification) int
## Authentication

We provide bearer tokens for all trusted sources, you just need to include your token in HTTP request headers.

Places-source tokens are scoped with an `import_origins` JSON array. Use the source origin, for example `["coinos"]`, to restrict a token to one vendor. Use `["*"]` for a token that can manage submissions for all origins.
41 changes: 38 additions & 3 deletions src/db/main/access_token/blocking_queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,36 @@ pub fn insert(
secret: &str,
roles: &[Role],
conn: &Connection,
) -> Result<AccessToken> {
insert_with_import_origins(user_id, name, secret, roles, &[], conn)
}

pub fn insert_with_import_origins(
user_id: i64,
name: &str,
secret: &str,
roles: &[Role],
import_origins: &[String],
conn: &Connection,
) -> Result<AccessToken> {
let roles: Vec<String> = roles.iter().map(|it| it.to_string()).collect();
let sql = format!(
r#"
INSERT INTO {TABLE} ({UserId}, {Name}, {Secret}, {Roles})
VALUES (?1, ?2, ?3, json(?4))
INSERT INTO {TABLE} ({UserId}, {Name}, {Secret}, {Roles}, {ImportOrigins})
VALUES (?1, ?2, ?3, json(?4), json(?5))
RETURNING {projection}
"#,
projection = AccessToken::projection(),
);
conn.query_row(
&sql,
params![user_id, name, secret, serde_json::to_string(&roles)?],
params![
user_id,
name,
secret,
serde_json::to_string(&roles)?,
serde_json::to_string(import_origins)?,
],
AccessToken::mapper(),
)
.map_err(Into::into)
Expand Down Expand Up @@ -92,10 +109,28 @@ mod test {
assert_eq!(Some(name), selected_token.name.as_deref());
assert_eq!(secret, selected_token.secret);
assert_eq!(roles, selected_token.roles);
assert!(selected_token.import_origins.is_empty());
assert!(selected_token.deleted_at.is_none());
Ok(())
}

#[test]
fn insert_with_import_origins() -> Result<()> {
let conn = conn();
let import_origins = vec!["square".to_string(), "coinos".to_string()];
let inserted_token = super::insert_with_import_origins(
2,
"name",
"secret",
&[Role::PlacesSource],
&import_origins,
&conn,
)?;
let selected_token = super::select_by_id(inserted_token.id, &conn)?;
assert_eq!(import_origins, selected_token.import_origins);
Ok(())
}

#[test]
fn select_all() -> Result<()> {
let conn = conn();
Expand Down
24 changes: 24 additions & 0 deletions src/db/main/access_token/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,30 @@ pub async fn insert(
.await?
}

#[cfg(test)]
pub async fn insert_with_import_origins(
user_id: i64,
name: String,
secret: String,
roles: Vec<Role>,
import_origins: Vec<String>,
pool: &Pool,
) -> Result<AccessToken> {
pool.get()
.await?
.interact(move |conn| {
blocking_queries::insert_with_import_origins(
user_id,
&name,
&secret,
&roles,
&import_origins,
conn,
)
})
.await?
}

pub async fn select_by_secret(secret: String, pool: &Pool) -> Result<AccessToken> {
pool.get()
.await?
Expand Down
11 changes: 11 additions & 0 deletions src/db/main/access_token/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub enum Columns {
Name,
Secret,
Roles,
ImportOrigins,
CreatedAt,
UpdatedAt,
DeletedAt,
Expand All @@ -27,6 +28,7 @@ pub struct AccessToken {
pub name: Option<String>,
pub secret: String,
pub roles: Vec<Role>,
pub import_origins: Vec<String>,
pub created_at: OffsetDateTime,
pub updated_at: OffsetDateTime,
pub deleted_at: Option<OffsetDateTime>,
Expand All @@ -42,6 +44,7 @@ impl AccessToken {
Columns::Name,
Columns::Secret,
Columns::Roles,
Columns::ImportOrigins,
Columns::CreatedAt,
Columns::UpdatedAt,
Columns::DeletedAt,
Expand All @@ -61,6 +64,9 @@ impl AccessToken {
name: row.get(Columns::Name.as_ref())?,
secret: row.get(Columns::Secret.as_ref())?,
roles: Self::parse_roles(row.get(Columns::Roles.as_ref())?)?,
import_origins: Self::parse_import_origins(
row.get(Columns::ImportOrigins.as_ref())?,
)?,
created_at: row.get(Columns::CreatedAt.as_ref())?,
updated_at: row.get(Columns::UpdatedAt.as_ref())?,
deleted_at: row.get(Columns::DeletedAt.as_ref())?,
Expand All @@ -76,4 +82,9 @@ impl AccessToken {
.filter_map(|s| Role::from_str(&s).ok())
.collect())
}

fn parse_import_origins(column_value: Value) -> rusqlite::Result<Vec<String>> {
serde_json::from_value(column_value)
.map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))
}
}
22 changes: 22 additions & 0 deletions src/db/main/migrations/101.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
ALTER TABLE access_token ADD COLUMN import_origins TEXT NOT NULL DEFAULT '[]';
UPDATE access_token
SET import_origins = json_array('*')
WHERE EXISTS (
SELECT 1
FROM json_each(access_token.roles)
WHERE json_each.value = 'places_source'
)
OR user_id IN (
SELECT id
FROM user
WHERE EXISTS (
SELECT 1
FROM json_each(user.roles)
WHERE json_each.value = 'places_source'
)
);
DROP TRIGGER acess_token_updated_at;
CREATE TRIGGER acess_token_updated_at UPDATE OF user_id, name, secret, roles, import_origins, created_at, deleted_at ON access_token
BEGIN
UPDATE access_token SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ') WHERE id = old.id;
END;
1 change: 1 addition & 0 deletions src/db/main/place_submission/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod blocking_queries;
pub mod queries;
pub mod schema;
pub mod vendor;
Loading
Loading