How do you create an actix-web HttpServer with session-based authentication? - authentication

I'm working on an internal API with which approved users can read from and insert into a database. My intention is for this program to run on our local network, and for multiple users or applications to be able to access it.
In its current state it functions as long as the user is running a local instance of the client and does all their work under localhost. However, when the same user attempts to log in from their IP address, the session is not stored and nothing can be accessed. The result is the same when attempting to connect from another computer on the network to a computer running the client.
Before commenting or answering, please be aware that this is my first time implementing authentication. Any mistakes or egregious errors on my part are simply out of ignorance.
My Cargo.toml file includes the following dependencies:
actix-session = { version = "0.7.1", features = ["cookie-session"] }
actix-web = "^4"
argon2 = "0.4.1"
rand_core = "0.6.3"
reqwest = "0.11.11"
serde = { version = "1.0.144", features = ["derive"] }
serde_json = "1.0.85"
sqlx = { version = "0.6.1", features = ["runtime-actix-rustls", "mysql", "macros"] }
Here are the contents of main.rs:
use actix_session::storage::CookieSessionStore;
use actix_session::SessionMiddleware;
use actix_web::cookie::Key;
use actix_web::web::{get, post, Data, Path};
use actix_web::{HttpResponse, Responder};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let secret_key = Key::generate();
// Load or create a new config file.
// let settings = ...
// Create a connection to the database.
let pool = sqlx::mysql::MySqlPoolOptions::new()
.connect(&format!(
"mysql://{}:{}#{}:{}/mydb",
env!("DB_USER"),
env!("DB_PASS"),
env!("DB_HOST"),
env!("DB_PORT"),
))
.await
.unwrap();
println!(
"Application listening on {}:{}",
settings.host,
settings.port,
);
// Instantiate the application and add routes for each handler.
actix_web::HttpServer::new(move || {
let logger = actix_web::middleware::Logger::default();
actix_web::App::new()
.wrap(SessionMiddleware::new(
CookieSessionStore::default(),
secret_key.clone(),
))
.wrap(logger)
.app_data(Data::new(pool.clone()))
/*
Routes that return all rows from a database table.
*/
/*
Routes that return a webpage.
*/
.route("/new", get().to(new))
.route("/login", get().to(login))
.route("/register", get().to(register))
/*
Routes that deal with authentication.
*/
.route("/register", post().to(register_user))
.route("/login", post().to(login_user))
.route("/logout", get().to(logout_user))
/*
Routes that handle POST requests.
*/
})
.bind(format!("{}:{}", settings.host, settings.port))?
.run()
.await
}
The code involving authentication is as follows:
use crate::model::User;
use actix_session::Session;
use actix_web::web::{Data, Form};
use actix_web::{error::ErrorUnauthorized, HttpResponse};
use argon2::password_hash::{rand_core::OsRng, PasswordHasher, SaltString};
use argon2::{Argon2, PasswordHash, PasswordVerifier};
use sqlx::{MySql, Pool};
#[derive(serde::Serialize)]
pub struct SessionDetails {
user_id: u32,
}
#[derive(Debug, sqlx::FromRow)]
pub struct AuthorizedUser {
pub id: u32,
pub username: String,
pub password_hash: String,
pub approved: bool,
}
pub fn check_auth(session: &Session) -> Result<u32, actix_web::Error> {
match session.get::<u32>("user_id").unwrap() {
Some(user_id) => Ok(user_id),
None => Err(ErrorUnauthorized("User not logged in.")),
}
}
pub async fn register_user(
data: Form<User>,
pool: Data<Pool<MySql>>,
) -> Result<String, Box<dyn std::error::Error>> {
let data = data.into_inner();
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
let password_hash = argon2
.hash_password(data.password.as_bytes(), &salt)
.unwrap()
.to_string();
// Use to verify.
// let parsed_hash = PasswordHash::new(&hash).unwrap();
const INSERT_QUERY: &str =
"INSERT INTO users (username, password_hash) VALUES (?, ?) RETURNING id;";
let fetch_one: Result<(u32,), sqlx::Error> = sqlx::query_as(INSERT_QUERY)
.bind(data.username)
.bind(password_hash)
.fetch_one(&mut pool.acquire().await.unwrap())
.await;
match fetch_one {
Ok((user_id,)) => Ok(user_id.to_string()),
Err(err) => Err(Box::new(err)),
}
}
pub async fn login_user(
session: Session,
data: Form<User>,
pool: Data<Pool<MySql>>,
) -> Result<HttpResponse, Box<dyn std::error::Error>> {
let data = data.into_inner();
let fetched_user: AuthorizedUser = match sqlx::query_as(
"SELECT id, username, password_hash, approved FROM users WHERE username = ?;",
)
.bind(data.username)
.fetch_one(&mut pool.acquire().await?)
.await
{
Ok(fetched_user) => fetched_user,
Err(e) => return Ok(HttpResponse::NotFound().body(format!("{e:?}"))),
};
let parsed_hash = PasswordHash::new(&fetched_user.password_hash).unwrap();
match Argon2::default().verify_password(&data.password.as_bytes(), &parsed_hash) {
Ok(_) => {
if !fetched_user.approved {
return Ok(
HttpResponse::Unauthorized().body("This account has not yet been approved.")
);
}
session.insert("user_id", &fetched_user.id)?;
session.renew();
Ok(HttpResponse::Ok().json(SessionDetails {
user_id: fetched_user.id,
}))
}
Err(_) => Ok(HttpResponse::Unauthorized().body("Incorrect password.")),
}
}
pub async fn logout_user(session: Session) -> HttpResponse {
if check_auth(&session).is_err() {
return HttpResponse::NotFound().body("No user logged in.");
}
session.purge();
HttpResponse::SeeOther()
.append_header(("Location", "/login"))
.body(format!("User logged out successfully."))
}
I've set my client up to run with host 0.0.0.0 on port 80, but with the little networking knowledge I have that's the best I could think to do — I'm lost here. Any help would be greatly appreciated.

As it turns out, the cookie was not being transmitted because our local network is not using https.
Changing this...
.wrap(SessionMiddleware::new(
CookieSessionStore::default(),
secret_key.clone(),
))
to the following...
.wrap(
SessionMiddleware::builder(CookieSessionStore::default(), secret_key.clone())
.cookie_secure(false)
.build(),
)
solves the issue.

Related

Get all transactions for an NFT on Solana

I want to collect all transactions for an NFT.
For example, you can display all transactions here:
https://explorer.solana.com/address/2Nzt8TYeAfgJDftKzkb7rgYShVvyXTR7cPVvpqaZ2a4V
or here:
https://solscan.io/token/2Nzt8TYeAfgJDftKzkb7rgYShVvyXTR7cPVvpqaZ2a4V#txs
But is there any way to do this with the API?
I checked
solana-py: https://michaelhly.github.io/solana-py/
and solscan api: https://public-api.solscan.io/docs/
But I could not find a way to do it.
You can use the getSignaturesForAddress RPC method on the mint address and walk backward to get all the transactions.
Here is an example in JS:
import {
Connection,
clusterApiUrl,
ConfirmedSignatureInfo,
PublicKey,
} from "#solana/web3.js";
const connection = new Connection(clusterApiUrl("mainnet-beta"));
export const getTxs = async (connection: Connection, pubkey: PublicKey) => {
const txs: ConfirmedSignatureInfo[] = [];
// Walk backward
let lastTransactions = await connection.getConfirmedSignaturesForAddress2(
pubkey
);
let before = lastTransactions[lastTransactions.length - 1].signature;
txs.push(...lastTransactions);
while (true) {
const newTransactions = await connection.getConfirmedSignaturesForAddress2(
pubkey,
{
before,
}
);
if (newTransactions.length === 0) break;
txs.push(...newTransactions);
before = newTransactions[newTransactions.length - 1].signature;
}
return txs;
};
getTxs(
connection,
new PublicKey("2Nzt8TYeAfgJDftKzkb7rgYShVvyXTR7cPVvpqaZ2a4V")
);
The equivalent method in Solana.py is this one https://michaelhly.github.io/solana-py/rpc/api/#solana.rpc.api.Client.get_signatures_for_address

Write rows to BigQuery via nodejs BigQuery Storage Write API

It seems quite new, but just hoping someone here has been able to use nodejs to write directly to BigQuery storage using #google-cloud/bigquery-storage.
There is an explanation of how the overall backend API works and how to write a collection of rows atomically using BigQuery Write API but no such documentation for nodejs yet. A recent release 2.7.0 documents the addition of said feature but there is no documentation, and the code is not easily understood.
There is an open issue requesting an example but thought I'd try my luck to see if anyone has been able to use this API yet.
Suppose you have a BigQuery table called student with three columns id,name and age. Following steps will get you to load data into the table with nodejs storage write api.
Define student.proto file as follows
syntax = "proto2";
message Student {
required int64 id = 1;
optional string name = 2;
optional int64 age = 3;
}
Run the following at the command prompt
protoc --js_out=import_style=commonjs,binary:. student.proto
It should generate student_pb.js file in the current directory.
Write the following js code in the current directory and run it
const {BigQueryWriteClient} = require('#google-cloud/bigquery-storage').v1;
const st = require('./student_pb.js')
const type = require('#google-cloud/bigquery-storage').protos.google.protobuf.FieldDescriptorProto.Type
const mode = require('#google-cloud/bigquery-storage').protos.google.cloud.bigquery.storage.v1.WriteStream.Type
const storageClient = new BigQueryWriteClient();
const parent = `projects/${project}/datasets/${dataset}/tables/student`
var writeStream = {type: mode.PENDING}
var student = new st.Student()
var protoDescriptor = {}
protoDescriptor.name = 'student'
protoDescriptor.field = [{'name':'id','number':1,'type':type.TYPE_INT64},{'name':'name','number':2,'type':type.TYPE_STRING},{'name':'age','number':3,'type':type.TYPE_INT64}]
async function run() {
try {
var request = {
parent,
writeStream
}
var response = await storageClient.createWriteStream(request);
writeStream = response[0].name
var serializedRows = []
//Row 1
student.setId(1)
student.setName('st1')
student.setAge(15)
serializedRows.push(student.serializeBinary())
//Row 2
student.setId(2)
student.setName('st2')
student.setAge(15)
serializedRows.push(student.serializeBinary())
var protoRows = {
serializedRows
}
var proto_data = {
writerSchema: {protoDescriptor},
rows: protoRows
}
// Construct request
request = {
writeStream,
protoRows: proto_data
};
// Insert rows
const stream = await storageClient.appendRows();
stream.on('data', response => {
console.log(response);
});
stream.on('error', err => {
throw err;
});
stream.on('end', async () => {
/* API call completed */
try {
var response = await storageClient.finalizeWriteStream({name: writeStream})
response = await storageClient.batchCommitWriteStreams({parent,writeStreams: [writeStream]})
}
catch(err) {
console.log(err)
}
});
stream.write(request);
stream.end();
}
catch(err) {
console.log(err)
}
}
run();
Make sure your environment variables are set correctly to point to the file containing google cloud credentials.
Change project and dataset values accordingly.

How to send a message when a trello card is moved to a certain list

I’m currently making a bot that informs me when a card is moved to the list titled Passed Applications.
I have already made the code and it basically sends a message once a card is moved to the specific list, however, it will randomly send a message and pull a card minutes/hours after it has already been pulled and sent the message.
What I’ve done so far is:
trello.js
var Trello = require("node-trello"),
EventEmitter = require("events").EventEmitter,
extend = require("extend"),
config,
trello,
timer,
e;
module.exports = function(options) {
var defaults = {
pollFrequency: 1000 * 60,
minId: 0,
trello: {
key: "",
token: "",
boards: []
},
start: true
};
e = new EventEmitter();
config = extend(true, defaults, options);
trello = new Trello(
process.env.TRELLO_API_KEY,
process.env.TRELLO_OAUTH_TOKEN
);
if (config.start) {
process.nextTick(function() {
start(config.pollFrequency, true);
});
}
function start(frequency, immediate) {
if (timer) {
return;
}
frequency = frequency || config.pollFrequency;
timer = setInterval(poll, frequency);
if (immediate) {
poll();
}
}
function poll() {
config.trello.boards.forEach(function(boardId) {
getBoardActivity(boardId);
});
}
function getBoardActivity(boardId) {
trello.get("/1/boards/" + boardId + "/actions", function(err, resp) {
if (err) {
return e.emit("trelloError", err);
}
var boardActions = resp.reverse();
var actionId;
for (var ix in boardActions) {
actionId = parseInt(boardActions[ix].id, 16);
if (actionId <= config.minId) {
continue;
}
var eventType = boardActions[ix].type;
e.emit(eventType, boardActions[ix], boardId);
}
config.minId = Math.max(config.minId, actionId);
e.emit("maxId", config.minId);
});
}
index.js
const conf = JSON.parse(fs.readFileSync("trelloconfig.json"));
let latestActivityID = fs.existsSync("./latestActivityID") ?
fs.readFileSync("./latestActivityID") :
0;
const eventEnabled = type =>
conf.enabledEvents.length > 0 ? conf.enabledEvents.includes(type) : true;
const TrelloEvents = require("./trello.js");
const events = new TrelloEvents({
pollFrequency: 60000,
minId: latestActivityID,
start: false,
trello: {
boards: conf.boardIDs,
key: process.env.TRELLO_API_KEY,
token: process.env.TRELLO_OAUTH_TOKEN
}
});
client.on("ready", () => {
events.start();
console.log(`[STATUS CHANGE] ${client.user.username} is now online.`);
client.user.setActivity("Cookout Grill");
});
events.on("updateCard", (event, board) => {
if (event.data.old.hasOwnProperty("idList")) {
if (!eventEnabled(`cardListChanged`)) return;
if (event.data.listAfter.name === "Passed Applications") {
let robloxId = event.data.card.name.split(" | ")[0];
client.channels.get("730839109236424756").send(robloxId);
if (database.find(x => x.RobloxUser === robloxId)) {
let data = database.find(x => x.RobloxUser === robloxId);
const person = client.users.get(data.DiscordID);
let embed = new discord.RichEmbed()
.setThumbnail(
"https://www.roblox.com/bust-thumbnail/image?userId=" +
data.RobloxID +
"&width=420&height=420&format=png"
)
.setTitle("APPLICATION RESULTS | Passed")
.setColor("3ef72d")
.setFooter("Cookout Grill", client.user.avatarURL)
.setDescription(
"Greetings, **" +
data.RobloxUser +
"**!\n\nAfter extensive review by our Management Team, we have decided to accept your Trainee Application at Cookout Grill. We believe that your application showed that you’re ready to become a staff member at our establishment.\n\nWe would like to congratulate you on passing your Trainee Application. Your application met our critical expectations and requirements in order to pass.\n\nIn order to work your way up throughout the staff ranks, you must attend a training at [Cookout Grill’s Training Center](https://www.roblox.com/groups/5634772/Cookout-Grill#!/about) during the specific session times. If you’re unable to attend one of our designated sessions, feel free to schedule a private session with a member of our Management Team.\n\nWe wish you the best of luck in continuing throughout the staff ranks at Cookout Grill. If you have any further questions, please do not hesitate to create a ticket in our main Discord Server."
)
.addField("**NEW RANK**", "`Trainee`");
person.send(embed);
roblox.message(
event.data.card.name.split(" | ")[1],
"APPLICATION RESULTS | Passed",
"Greetings, **" +
data.RobloxUser +
"**!\n\nAfter extensive review by our Management Team, we have decided to accept your Trainee Application at Cookout Grill.\n\nWe would like to congratulate you on passing your Trainee Application. Your application met our critical expectations and requirements in order to pass.\n\nIn order to work your way up throughout the staff ranks, you must attend a training at Cookout Grill’s Training Center during the specific session times. If you’re unable to attend one of our designated sessions, feel free to schedule a private session with a member of our Management Team.\n\nWe wish you the best of luck in continuing throughout the staff ranks at Cookout Grill."
);
}
let embed2 = new discord.RichEmbed()
.setTitle(`Card list changed!`)
.setDescription(
`**CARD:** ${
event.data.card.name
} — **[CARD LINK](https://trello.com/c/${
event.data.card.shortLink
})**\n\n**EVENT:** Card moved to list __${
event.data.listAfter.name
}__ from list __${event.data.listBefore.name}__ by **[${
conf.realNames
? event.memberCreator.fullName
: event.memberCreator.username
}](https://trello.com/${event.memberCreator.username})**`
);
client.channels.get("730839109236424756").send(embed2);
Trello.addCommentToCard(
event.data.card.id,
"User has been ranked.",
function(error, trelloCard) {
console.log(error);
}
);
} else return;
} else return;
});

How do I iterate over a Vec of functions returning Futures in Rust?

Is it possible to loop over a Vec, calling a method that returns a Future on each, and build a chain of Futures, to be evaluated (eventually) by the consumer? Whether to execute the later Futures would depend on the outcome of the earlier Futures in the Vec.
To clarify:
I'm working on an application that can fetch data from an arbitrary set of upstream sources.
Requesting data would check with each of the sources, in turn. If the first source had an error (Err), or did not have the data available (None), then the second source would be tried, and so on.
Each source should be tried exactly once, and no source should be tried until all of the sources before have returned their results. Errors are logged, but otherwise ignored, passing the query to the next upstream data source.
I have some working code that does this for fetching metadata:
/// Attempts to read/write data to various external sources. These are
/// nested types, because a data source may exist as both a reader and a writer
struct StoreManager {
/// Upstream data sources
readers: Vec<Rc<RefCell<StoreRead>>>,
/// Downstream data sinks
writers: Vec<Rc<RefCell<StoreWrite>>>,
}
impl StoreRead for StoreManager {
fn metadata(self: &Self, id: &Identifier) -> Box<Future<Option<Metadata>, Error>> {
Box::new(ok(self.readers
.iter()
.map(|store| {
executor::block_on(store.borrow().metadata(id)).unwrap_or_else(|err| {
error!("Error on metadata(): {:?}", err);
None
})
})
.find(Option::is_some)
.unwrap_or(None)))
}
}
Aside from my unhappiness with all of the Box and Rc/RefCell nonsense, my real concern is with the executor::block_on() call. It blocks, waiting for each Future to return a result, before continuing to the next.
Given that it's possible to call fn_returning_future().or_else(|_| other_fn()) and so on, is it possible to build up a dynamic chain like this? Or is it a requirement to fully evaluate each Future in the iterator before moving to the next?
You can use stream::unfold to convert a single value into a stream. In this case, we can use the IntoIter iterator as that single value.
use futures::{executor, stream, Stream, TryStreamExt}; // 0.3.4
type Error = Box<dyn std::error::Error>;
type Result<T, E = Error> = std::result::Result<T, E>;
async fn network_request(val: i32) -> Result<i32> {
// Just for demonstration, don't do this in a real program
use std::{
thread,
time::{Duration, Instant},
};
thread::sleep(Duration::from_secs(1));
println!("Resolving {} at {:?}", val, Instant::now());
Ok(val * 100)
}
fn requests_in_sequence(vals: Vec<i32>) -> impl Stream<Item = Result<i32>> {
stream::unfold(vals.into_iter(), |mut vals| async {
let val = vals.next()?;
let response = network_request(val).await;
Some((response, vals))
})
}
fn main() {
let s = requests_in_sequence(vec![1, 2, 3]);
executor::block_on(async {
s.try_for_each(|v| async move {
println!("-> {}", v);
Ok(())
})
.await
.expect("An error occurred");
});
}
Resolving 1 at Instant { tv_sec: 6223328, tv_nsec: 294631597 }
-> 100
Resolving 2 at Instant { tv_sec: 6223329, tv_nsec: 310839993 }
-> 200
Resolving 3 at Instant { tv_sec: 6223330, tv_nsec: 311005834 }
-> 300
To ignore Err and None, you have to shuttle the Error over to the Item, making the Item type a Result<Option<T>, Error>:
use futures::{executor, stream, Stream, StreamExt}; // 0.3.4
type Error = Box<dyn std::error::Error>;
type Result<T, E = Error> = std::result::Result<T, E>;
async fn network_request(val: i32) -> Result<Option<i32>> {
// Just for demonstration, don't do this in a real program
use std::{
thread,
time::{Duration, Instant},
};
thread::sleep(Duration::from_secs(1));
println!("Resolving {} at {:?}", val, Instant::now());
match val {
1 => Err("boom".into()), // An error
2 => Ok(None), // No data
_ => Ok(Some(val * 100)), // Success
}
}
fn requests_in_sequence(vals: Vec<i32>) -> impl Stream<Item = Result<Option<i32>>> {
stream::unfold(vals.into_iter(), |mut vals| async {
let val = vals.next()?;
let response = network_request(val).await;
Some((response, vals))
})
}
fn main() {
executor::block_on(async {
let s = requests_in_sequence(vec![1, 2, 3]);
let s = s.filter_map(|v| async move { v.ok() });
let s = s.filter_map(|v| async move { v });
let mut s = s.boxed_local();
match s.next().await {
Some(v) => println!("First success: {}", v),
None => println!("No successful requests"),
}
});
}
Resolving 1 at Instant { tv_sec: 6224229, tv_nsec: 727216392 }
Resolving 2 at Instant { tv_sec: 6224230, tv_nsec: 727404752 }
Resolving 3 at Instant { tv_sec: 6224231, tv_nsec: 727593740 }
First success: 300
is it possible to build up a dynamic chain like this
Yes, by leveraging async functions:
use futures::executor; // 0.3.4
type Error = Box<dyn std::error::Error>;
type Result<T, E = Error> = std::result::Result<T, E>;
async fn network_request(val: i32) -> Result<Option<i32>> {
// Just for demonstration, don't do this in a real program
use std::{
thread,
time::{Duration, Instant},
};
thread::sleep(Duration::from_secs(1));
println!("Resolving {} at {:?}", val, Instant::now());
match val {
1 => Err("boom".into()), // An error
2 => Ok(None), // No data
_ => Ok(Some(val * 100)), // Success
}
}
async fn requests_in_sequence(vals: Vec<i32>) -> Result<i32> {
let mut vals = vals.into_iter().peekable();
while let Some(v) = vals.next() {
match network_request(v).await {
Ok(Some(v)) => return Ok(v),
Err(e) if vals.peek().is_none() => return Err(e),
Ok(None) | Err(_) => { /* Do nothing and try the next source */ }
}
}
Err("Ran out of sources".into())
}
fn main() {
executor::block_on(async {
match requests_in_sequence(vec![1, 2, 3]).await {
Ok(v) => println!("First success: {}", v),
Err(e) => println!("No successful requests: {}", e),
}
});
}
See also:
Creating Diesel.rs queries with a dynamic number of .and()'s
is it a requirement to fully evaluate each Future in the iterator before moving to the next
Isn't that part of your own requirements? Emphasis mine:
Requesting data would check with each of the sources, in turn. If the first source had an error (Err), or did not have the data available (None), then the second source would be tried

Setting Up Permissions with Realm Object Server for shared Realms

I am trying to set up a shared realm, which all users have access to. I also intend for users to create realms as required, which represent projects. I would like to give the user permissions to read and write any project realms that they have created, however have read access to all of the other users' realms. I would also like the ability to assign write permissions as required, to other users, without giving them admin status in the Realm Object Server.
I am thinking that my application will allow a user to login with minimal permissions, and having a second admin user working in the background, for managing permissions. The admin user would not be exposed to the user.
I have been following an example provided at https://github.com/realm-demos/realm-teamwork-MR, however haven't had any success with setting up permissions. My test case is as follows:
import UIKit
import RealmSwift
import Realm
let ApplicationName = "SyncTest"
let syncHost = "127.0.0.1" // The realm-oject-server is hosted on AWS, however changed for this example to keep user data private. HTTPS has also been implemented.
let syncAuthURL = URL(string: "https://\(syncHost):9443")!
let commonRealmURL:URL = URL(string: "realms://\(syncHost):9443/\(ApplicationName)-CommonRealm")!
class Dog: Object {
dynamic var name = ""
dynamic var age = 0
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
updateUserPermissions()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
private func updateUserPermissions() {
// Create the callback block that will perform the request
let logInBlock: ((SyncCredentials) -> Void) = { credentials in
SyncUser.logIn(with: credentials, server: syncAuthURL, timeout: 30, onCompletion: { (user, error) in
DispatchQueue.main.async {
// Display an error message if the login failed
if let error = error {
self.showError(title: "Unable to Sign In", message: error.localizedDescription)
return
}
guard let user = user else { return }
print("ID: \(String(describing: user.identity)), Total Users Logged In: \(SyncUser.all.count)")
let config = Realm.Configuration(syncConfiguration: SyncConfiguration(user: user, realmURL: commonRealmURL), objectTypes: [Dog.self])
let adminRealm:Realm = try! Realm(configuration: config)
let permission = SyncPermissionValue(realmPath: adminRealm.configuration.syncConfiguration!.realmURL.path,
username: "user#host.com",
accessLevel: .write)
user.applyPermission(permission) { error in
if let error = error {
self.showError(title: "Unable to Apply Permissions", message: error.localizedDescription)
return
}
}
let myDog = Dog()
myDog.name = "admin" + Date().description
myDog.age = 1
try! adminRealm.write {
adminRealm.add(myDog)
}
let results = adminRealm.objects(Dog.self)
print("Number of results after admin login: \(results.count)")
self.logInUser()
}
})
}
let credentials = SyncCredentials.usernamePassword(username: "admin#host.com", password: "admin", register: false)
logInBlock(credentials)
}
private func showError(title: String, message: String) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alertController, animated: true, completion: nil)
}
private func logInUser() {
// Create the callback block that will perform the request
let logInBlock: ((SyncCredentials) -> Void) = { credentials in
SyncUser.logIn(with: credentials, server: syncAuthURL, timeout: 30, onCompletion: { (user, error) in
DispatchQueue.main.async {
// Display an error message if the login failed
if let error = error {
self.showError(title: "Unable to Sign In", message: error.localizedDescription)
return
}
guard let user = user else { return }
let config = Realm.Configuration(syncConfiguration: SyncConfiguration(user: user, realmURL: commonRealmURL), objectTypes: [Dog.self])
let userRealm = try! Realm(configuration: config)
let myDog = Dog()
myDog.name = "user" + Date().description
myDog.age = 2
try! userRealm.write {
userRealm.add(myDog)
}
let results = userRealm.objects(Dog.self)
print("Number of results after user login: \(results.count)")
}
})
}
let credentials = SyncCredentials.usernamePassword(username: "user#host.com", password: "user", register: false)
logInBlock(credentials)
}
}
Any ideas how I can successfully assign permissions with a background admin user? Or would I be better off using a different structure for my databases? Thanks!