9 unstable releases (3 breaking)

Uses old Rust 2015

0.4.3 Jul 30, 2018
0.4.2 Jul 22, 2018
0.3.1 Jul 12, 2018
0.2.1 Jul 1, 2018
0.1.2 Jun 30, 2018

#1156 in Database interfaces

BSD-3-Clause

39KB
627 lines

Actix Database Identity Provider

Build Status Crates.io docs.rs

SQL database (diesel) integration for actix framework

Description

Implements a SQL backend for Actix-Web's identity middleware. Does not perform any authentication, only "remembers" the user when told to. The returned header containing the authorized token is configurable via the SqlIdentityBuilder::response_header() method.

Normal server application flow:

  • Application authenticates a user
  • Application calls remember() with a user-identifiable string
  • SQL identity middleware generates a new token and embeds it in the response header
  • Application returns response (with header)

From here, normal flow according to identity middleware guide can be followed

Normal client application flow:

  • Client POSTs to a login endpoint, is authorized by server
  • Client receives response, extracts the authorization token in the specified header
  • On future requests (including logout), the client builds a bearer authentication header with the returned token

SQL Variants supported

  • SQLite
  • MySQL
  • Postgres

Features

Default: SQLite, MySQL, Postgres

sqlite: Include SQLite support

mysql: Include MySQL support

postgres: Include PostgreSQL supprt

Database Requirements

This crate requires a table named identities with the following fields:

Field Type Constraints Description
id BIGINT PRIMARY KEY, AUTO INCREMENT A unique id that is not the token, for revoking sessions
token CHAR(32) NOT NULL, UNIQUE The auto-generated token field, will be used to lookup user
userid TEXT NOT NULL The user id to remember, probably a key in another table
ip TEXT The IP the user most recently connected from
useragent TEXT The user-agent of the most recent connection
created TIMESTAMP NOT NULL Timestamp (w/out timezone) this token was created
modified TIMESTAMP NOT NULL Timestamp (w/out timezone) this token was last used

Example SQL files for SQLite, MySQL, and PostgreSQL are available int the sql/ folder on the repository

Server Example

extern crate actix_web;
extern crate actix_web_sql_identity;

use actix_web::{http, server, App, HttpRequest, Responder};
use actix_web::middleware::identity::{IdentityService, RequestIdentity};
use actix_web_sql_identity::SqlIdentityBuilder;

const POOL_SIZE: usize = 3;     // Number of connections per pool

fn login(mut req: HttpRequest) -> impl Responder {
    // Should pull username/id from request
    req.remember("username_or_id".to_string());
    "Logged in!".to_string()
}

fn profile(req: HttpRequest) -> impl Responder {
    if let Some(user) = req.identity() {
        format!("Hello, {}!", user)
    } else {
        "Hello, anonymous user!".to_string()
    }
}

fn logout(mut req: HttpRequest) -> impl Responder {
    req.forget();
    "Logged out!".to_string()
}

fn main() {
    server::new(|| {
		// Construct our policy, passing the address and any options
        let policy = SqlIdentityBuilder::new("sqlite://my.db")
            .pool_size(POOL_SIZE);

        App::new()
            .route("/login", http::Method::POST, login)
            .route("/profile", http::Method::GET, profile)
            .route("/logout", http::Method::POST, logout)
            .middleware(IdentityService::new(
                    policy.finish()
                        .expect("failed to connect to database")))
    })
    .bind("127.0.0.1:7070").unwrap()
    .run();
}

Client Example

extern crate reqwest;

#[macro_use]
extern crate hyper;

use hyper::header::{Authorization, Bearer, Headers};
use reqwest::{Client, Response};

// Build our custom header that will contain our returned token
header! { (XActixAuth, "X-Actix-Auth") => [String] }

/// Builds a GET request to send to the server, with optional authentication
///
/// # Arguments
///
/// * `client` - Client to build request with
/// * `uri` - Endpoint to target (e.g., /index, /profile, etc)
/// * `token` - An optional authentication token
fn build_get(client: &Client, uri: &str, token: Option<&str>) -> Response {
    let mut req = client
        .get(format!("http://127.0.0.1:7070{}", uri).as_str());

    if let Some(token) = token {
        let mut headers = Headers::new();
        headers.set(Authorization(Bearer {
            token: token.to_owned(),
        }));
        req.headers(headers);
    }

    let req = req.build()
        .expect("failed to build request");

    client.execute(req).expect("failed to send request")
}

/// Builds a POST request to send to the server, with optional authentication
///
/// # Arguments
///
/// * `client` - Client to build request with
/// * `uri` - Endpoint to target (e.g., /login, /logout)
/// * `token` - An optional authentication token
fn build_post(client: &Client, uri: &str, token: Option<&str>) -> Response {
    let mut req = client
        .post(format!("http://127.0.0.1:7070{}", uri).as_str());

    if let Some(token) = token {
        let mut headers = Headers::new();
        headers.set(Authorization(Bearer {
            token: token.to_owned(),
        }));
        req.headers(headers);
    }

    let req = req.build()
        .expect("failed to build request");

    client.execute(req).expect("failed to send request")
}

/// Pretty print a response
///
/// # Arguments
///
/// * `resp` - Response to print
fn print_response(resp: &mut Response) {
    let uri = resp.url().clone();
    println!("[{0: <15}] {1: <30} {2}", resp.status(), uri.as_str(), resp.text().expect("failed to read response"));
}

fn main() {
    let client = Client::new();

    // Get Index
    let mut resp = build_get(&client, "/", None); 
    print_response(&mut resp);

    // Get Profile (no auth)
    let mut resp = build_get(&client, "/profile", None);
    print_response(&mut resp);

    // Login
    let mut resp = build_post(&client, "/login", None);
    print_response(&mut resp);

    // Extract the auth token from the header
    // (Header field can be changed on server, default is used here)
    let hdrs = resp.headers();
    let token = hdrs.get::<XActixAuth>().unwrap();
    //println!("[token]: {:?}", token.0);

    // Get Profile (auth)
    let mut resp = build_get(&client, "/profile", Some(token.0.as_ref()));
    print_response(&mut resp);

    // Logout
    let mut resp = build_post(&client, "/logout", Some(token.0.as_str()));
    print_response(&mut resp);

    // Get Profile (auth)
    let mut resp = build_get(&client, "/profile", Some(token.0.as_ref()));
    print_response(&mut resp);
}

License

BSD 3-Clause

Author

Kevin Allison kvnallsn AT gmail.com

Dependencies

~27–38MB
~624K SLoC