aboutsummaryrefslogtreecommitdiff
path: root/plugins/VNLib.Plugins.Essentials.Auth.Social/src/LoginUriBuilder.cs
blob: da37fb7ab15e1eef80bf1e208644cfbadde6da90 (plain)
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
/*
* Copyright (c) 2024 Vaughn Nugent
* 
* Library: VNLib
* Package: VNLib.Plugins.Essentials.Auth.Social
* File: LoginUriBuilder.cs 
*
* LoginUriBuilder.cs is part of VNLib.Plugins.Essentials.Auth.Social which is part of the larger 
* VNLib collection of libraries and utilities.
*
* VNLib.Plugins.Essentials.Auth.Social is free software: you can redistribute it and/or modify 
* it under the terms of the GNU Affero General Public License as 
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* VNLib.Plugins.Essentials.Auth.Social is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program.  If not, see https://www.gnu.org/licenses/.
*/

using System;
using System.Text;
using System.Runtime.InteropServices;

using VNLib.Utils;
using VNLib.Utils.Memory;
using VNLib.Utils.Extensions;
using VNLib.Plugins.Essentials.Accounts;

namespace VNLib.Plugins.Essentials.Auth.Social
{
    /*
     * Construct the client's redirect url based on their login claim, which contains
     * a public key which can be used to encrypt the url so that only the client 
     * private-key holder can decrypt the url and redirect themselves to the 
     * target OAuth website. 
     * 
     * The result is an encrypted nonce that should guard against replay attacks and MITM
     */

    internal sealed record class LoginUriBuilder(OauthClientConfig Config)
    {
        private string? redirectUrl;
        private string? nonce;
        private Encoding _encoding = Encoding.UTF8;

        public LoginUriBuilder WithUrl(ReadOnlySpan<char> scheme, ReadOnlySpan<char> authority, ReadOnlySpan<char> path)
        {
            //Alloc stack buffer for url
            Span<char> buffer = stackalloc char[1024];

            //buffer writer for easier syntax
            ForwardOnlyWriter<char> writer = new(buffer);
            //first build the redirect url to re-encode it
            writer.Append(scheme);
            writer.Append("://");
            //Create redirect url (current page, default action is to authorize the client)
            writer.Append(authority);
            writer.Append(path);
            //url encode the redirect path and save it for later
            redirectUrl = Uri.EscapeDataString(writer.ToString());

            return this;
        }

        public LoginUriBuilder WithEncoding(Encoding encoding)
        {
            _encoding = encoding;
            return this;
        }

        public LoginUriBuilder WithNonce(string base32Nonce)
        {
            nonce = base32Nonce;
            return this;
        }

        public string Encrypt(HttpEntity client, IClientSecInfo secInfo)
        {
            //Alloc buffer and split it into binary and char buffers
            using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAllocNearestPage(8000);

            Span<byte> binBuffer = buffer.Span[2048..];
            Span<char> charBuffer = MemoryMarshal.Cast<byte, char>(buffer.Span[..2048]);


            /*
             * Build the character uri so we can encode it to binary, 
             * encrypt it and return it to the client
             */

            ForwardOnlyWriter<char> writer = new(charBuffer);

            //Append the config redirect path
            writer.Append(Config.AccessCodeUrl.OriginalString);
            //begin query arguments
            writer.Append("&client_id=");
            writer.Append(Config.ClientID.Value);
            //add the redirect url
            writer.Append("&redirect_uri=");
            writer.Append(redirectUrl);
            //Append the state parameter
            writer.Append("&state=");
            writer.Append(nonce);

            //Collect the written character data
            ReadOnlySpan<char> url = writer.AsSpan();

            //Separate bin buffers for encryption and encoding
            Span<byte> encryptionBuffer = binBuffer[1024..];
            Span<byte> encodingBuffer = binBuffer[..1024];

            //Encode the url to binary
            int byteCount = _encoding.GetBytes(url, encodingBuffer);

            //Encrypt the binary data
            ERRNO count = client.TryEncryptClientData(secInfo, encodingBuffer[..byteCount], encryptionBuffer);

            //base64 encode the encrypted
            return Convert.ToBase64String(encryptionBuffer[0..(int)count]);
        }
    }
}