1mod auth_wrapper;
2mod hmac_wrapper;
3mod shop_url;
4
5#[derive(Debug)]
6pub enum Error {
7 InvalidHmac,
8 InvalidShopUrl,
9 InvalidNonce,
10 ShopNotFound,
11}
12
13#[derive(Clone)]
14pub struct Credentials {
15 pub api_key: String,
16 pub secret: String,
17}
18
19impl Credentials {
20 pub fn new(api_key: String, secret: String) -> Self {
21 Credentials {
22 api_key: api_key,
23 secret: secret
24 }
25 }
26}
27
28#[derive(Clone, Copy)]
29pub enum AccessMode {
30 Offline,
31 Online,
32}
33
34impl AccessMode {
35 pub fn as_string(&self) -> &'static str {
36 match self {
37 AccessMode::Offline => "offline",
38 AccessMode::Online => "online"
39 }
40 }
41}
42
43#[derive(Clone)]
44pub struct ShopifyApp {
45 pub access_mode: AccessMode,
46 pub auth_callback_url: String,
47 pub credentials: Credentials,
48 pub host: String,
49 pub scopes: Vec<&'static str>
50}
51
52impl ShopifyApp {
53 pub fn validate_auth(
54 &self,
55 query_params: &Vec<(String, String)>,
56 validate_hmac: bool,
57 ) -> Result<(String, String), Error> {
58 if validate_hmac && !self.valid_hmac(query_params) {
59 return Err(Error::InvalidHmac);
60 }
61
62 let shop = match query_params
63 .into_iter()
64 .find(|(key, _)| "shop".to_string() == *key)
65 .map(|(_, val)| val)
66 {
67 Some(shop) => shop,
68 None => return Err(Error::ShopNotFound),
69 };
70
71 let nonce = ShopifyApp::new_nonce();
72
73 let redirect_uri = self.new_auth_uri(
74 &shop,
75 &format!(
76 "{host}{auth_callback_uri}",
77 host = self.host,
78 auth_callback_uri = self.auth_callback_url
79 ),
80 &nonce,
81 );
82
83 Ok((redirect_uri, nonce))
84 }
85
86 pub fn validate_auth_callback(
87 &self,
88 nonce: &str,
89 query_params: &Vec<(String, String)>,
90 ) -> Result<(), Error> {
91 if !self.valid_hmac(query_params) {
92 return Err(Error::InvalidHmac);
93 }
94
95 let shop = match query_params
96 .into_iter()
97 .find(|(key, _)| "shop".to_string() == *key)
98 .map(|(_, val)| val)
99 {
100 Some(shop) => shop,
101 None => return Err(Error::ShopNotFound),
102 };
103
104 if !ShopifyApp::shopify_url(shop) {
105 return Err(Error::InvalidShopUrl);
106 }
107
108 let req_nonce = match query_params
109 .into_iter()
110 .find(|(key, _)| "state".to_string() == *key)
111 .map(|(_, val)| val)
112 {
113 Some(shop) => shop,
114 None => return Err(Error::ShopNotFound),
115 };
116
117 if req_nonce != nonce {
118 return Err(Error::InvalidNonce);
119 }
120
121 Ok(())
122 }
123}