aboutsummaryrefslogtreecommitdiff
path: root/src/utils.rs
blob: e11a313daef40b29d2555b4ea2a36ffb79ec5206 (plain) (blame)
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//! A collection of functions for use with the OAuth2 API.
//!
//! This includes functions for easily generating URLs to redirect users to for
//! authorization.

pub use serenity_model::Permissions;

use constants::BASE_AUTHORIZE_URI;
use percent_encoding;
use super::Scope;
use std::fmt::Write;

/// Creates a URL for a simple bot authorization flow.
///
/// This is a special,
/// server-less and callback-less OAuth2 flow that makes it
/// easy for users to add bots to guilds.
///
/// # Examples
///
/// Create an authorization URL for a bot requiring the "Add Reactions" and
/// "Send Messages" permissions:
///
/// ```rust
/// extern crate serenity_model;
/// extern crate serenity_oauth;
///
/// # fn main() {
/// use serenity_model::Permissions;
///
/// let client_id = 249608697955745802;
/// let required = Permissions::ADD_REACTIONS | Permissions::SEND_MESSAGES;
/// let url = serenity_oauth::utils::bot_authorization_url(client_id, required);
///
/// // Assert that the expected URL is correct.
/// let expected = "https://discordapp.com/api/oauth2/authorize?client_id=249608697955745802&scope=bot&permissions=2112";
/// assert_eq!(url, expected);
/// # }
/// ```
pub fn bot_authorization_url(client_id: u64, permissions: Permissions)
    -> String {
    format!(
        "{}?client_id={}&scope=bot&permissions={}",
        BASE_AUTHORIZE_URI,
        client_id,
        permissions.bits(),
    )
}

/// Creates a URL for an authorization code grant.
///
/// This will create a URL to redirect the user to, requesting the given scopes
/// for your client ID.
///
/// The given `redirect_uri` will automatically be URL encoded.
///
/// A state _should_ be passed, as recommended by RFC 6749. It is a unique
/// identifier for the user's request. When Discord redirects the user to the
/// given redirect URI, it will append a `state` parameter to the query. It will
/// match the state that you have recorded for that user. If it does not, there
/// was likely a request interception.
///
/// As well as the callback URL having the same `state` appended in the query
/// parameters, this will also append a `code`.
///
/// # Examples
///
/// Produce an authorization code grant URL for your client, requiring the
/// [`Scope::Identify`] and [`Scope::GuildsJoin`] scopes, and an example of a
/// state:
///
/// **Note**: Please randomly generate this using a crate like `rand`.
///
/// ```rust
/// use serenity_oauth::Scope;
///
/// let client_id = 249608697955745802;
/// let scopes = [Scope::GuildsJoin, Scope::Identify];
/// let state = "15773059ghq9183habn";
/// let redirect_uri = "https://myapplication.website";
///
/// let url = serenity_oauth::utils::authorization_code_grant_url(
///     client_id,
///     &scopes,
///     Some(state),
///     redirect_uri,
/// );
///
/// // Assert that the URL is correct.
/// let expected = "https://discordapp.com/api/oauth2/authorize?response_type=code&client_id=249608697955745802&redirect_uri=https%3A%2F%2Fmyapplication.website&scope=guilds.join%20identify&state=15773059ghq9183habn";
/// assert_eq!(url, expected);
/// ```
///
/// [`Scope::GuildsJoin`]: enum.Scope.html#variant.GuildsJoin
/// [`Scope::Identify`]: enum.Scope.html#variant.Identify
pub fn authorization_code_grant_url(
    client_id: u64,
    scopes: &[Scope],
    state: Option<&str>,
    redirect_uri: &str,
) -> String {
    let mut base = String::from(BASE_AUTHORIZE_URI);
    let uri = percent_encoding::percent_encode(
        redirect_uri.as_bytes(),
        percent_encoding::USERINFO_ENCODE_SET,
    );

    let _ = write!(
        base,
        "?response_type=code&client_id={}&redirect_uri={}&scope=",
        client_id,
        uri,
    );

    let scope_count = scopes.len();

    for (i, scope) in scopes.iter().enumerate() {
        let _ = write!(base, "{}", scope);

        if i + 1 < scope_count {
            base.push_str("%20");
        }
    }

    if let Some(state) = state {
        let _ = write!(base, "&state={}", state);
    }

    base
}