1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
pub use facebook_fb_login_deauth_callback;

use std::{collections::HashMap, convert::Infallible, error, sync::Arc};

use facebook_fb_login_deauth_callback::{
    get::PASS_BACK_STATUS_CODE,
    post::{pass_back_with_signed_request, PassBackCallbackFn, SIGNED_REQUEST_FORM_KEY},
};
use warp::{
    http::{Response, StatusCode},
    hyper::Body,
    Filter,
};

pub trait Context: Send + Sync + Clone {
    fn get_app_secret(&self, app_id: u64) -> Result<String, Box<dyn error::Error + Send + Sync>>;
}

pub fn handle<C: Context>(
    path_prefix: String,
    ctx: C,
    callback: PassBackCallbackFn<'static, C>,
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
    get_filter(path_prefix.clone(), ctx.clone()).or(post_filter(
        path_prefix,
        ctx,
        Arc::new(callback),
    ))
}

fn get_filter<C: Context>(
    path_prefix: String,
    ctx: C,
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
    warp::path(path_prefix)
        .and(warp::path::param::<u64>())
        .and(warp::path::end())
        .and(warp::get())
        .and_then(move |app_id| {
            let ctx = ctx.clone();

            async move {
                let part: Result<Result<Response<Body>, warp::http::Error>, Infallible> = {
                    match ctx.get_app_secret(app_id) {
                        Ok(_) => Ok(Response::builder()
                            .status(PASS_BACK_STATUS_CODE)
                            .body("".into())),
                        Err(err) => Ok(Response::builder()
                            .status(StatusCode::INTERNAL_SERVER_ERROR)
                            .body(err.to_string().into())),
                    }
                };
                part
            }
        })
}

fn post_filter<C: Context>(
    path_prefix: String,
    ctx: C,
    callback: Arc<PassBackCallbackFn<'static, C>>,
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
    warp::path(path_prefix)
        .and(warp::path::param::<u64>())
        .and(warp::path::end())
        .and(warp::post())
        .and(warp::body::content_length_limit(1024 * 32).and(warp::body::form()))
        .and_then(move |app_id: u64, request_form: HashMap<String, String>| {
            let ctx = ctx.clone();
            let callback = callback.clone();

            async move {
                let part: Result<Result<Response<Body>, warp::http::Error>, Infallible> = {
                    match request_form.get(SIGNED_REQUEST_FORM_KEY) {
                        Some(signed_request) => match ctx.get_app_secret(app_id) {
                            Ok(app_secret) => {
                                let res = pass_back_with_signed_request(
                                    signed_request,
                                    &app_secret,
                                    ctx,
                                    callback,
                                )
                                .await;

                                Ok(Response::builder()
                                    .status(res.status_code)
                                    .body(res.body.into()))
                            }
                            Err(err) => Ok(Response::builder()
                                .status(StatusCode::INTERNAL_SERVER_ERROR)
                                .body(err.to_string().into())),
                        },
                        None => Ok(Response::builder()
                            .status(StatusCode::BAD_REQUEST)
                            .body("form invalid".into())),
                    }
                };
                part
            }
        })
}