Skip to content

Commit d103222

Browse files
Add session handling component
1 parent cd0e661 commit d103222

File tree

8 files changed

+150
-23
lines changed

8 files changed

+150
-23
lines changed

API/Protocol/IResponse.cs

+6
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ public interface IResponse : IDisposable
5252
/// </summary>
5353
bool HasCookies { get; }
5454

55+
/// <summary>
56+
/// Adds the given cookie to the cookie collection of this response.
57+
/// </summary>
58+
/// <param name="cookie">The cookie to be added</param>
59+
void SetCookie(Cookie cookie);
60+
5561
#endregion
5662

5763
#region Content

Engine/Protocol/Response.cs

+9
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,15 @@ internal Response()
7676

7777
#endregion
7878

79+
#region Functionality
80+
81+
public void SetCookie(Cookie cookie)
82+
{
83+
WriteableCookies[cookie.Name] = cookie;
84+
}
85+
86+
#endregion
87+
7988
#region IDisposable Support
8089

8190
private bool disposed = false;

Modules/Authentication.Web/Concern/WebAuthenticationBuilder.cs

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,32 @@
11
using GenHTTP.Api.Content;
2+
using GenHTTP.Api.Infrastructure;
23
using System;
34

45
namespace GenHTTP.Modules.Authentication.Web.Concern
56
{
67

78
public sealed class WebAuthenticationBuilder : IConcernBuilder
89
{
10+
private bool _AllowAnonymous;
11+
12+
private SessionConfig? _SessionConfig;
13+
914
private SetupConfig? _SetupConfig;
1015

1116
#region Functionality
1217

18+
public WebAuthenticationBuilder AllowAnonymous()
19+
{
20+
_AllowAnonymous = true;
21+
return this;
22+
}
23+
24+
public WebAuthenticationBuilder SessionHandling(SessionConfig sessionConfig)
25+
{
26+
_SessionConfig = sessionConfig;
27+
return this;
28+
}
29+
1330
public WebAuthenticationBuilder EnableSetup(SetupConfig setupConfig)
1431
{
1532
_SetupConfig = setupConfig;
@@ -18,7 +35,9 @@ public WebAuthenticationBuilder EnableSetup(SetupConfig setupConfig)
1835

1936
public IConcern Build(IHandler parent, Func<IHandler, IHandler> contentFactory)
2037
{
21-
return new WebAuthenticationConcern(parent, contentFactory, _SetupConfig);
38+
var sessionConfig = _SessionConfig ?? throw new BuilderMissingPropertyException("Sessions");
39+
40+
return new WebAuthenticationConcern(parent, contentFactory, _AllowAnonymous, sessionConfig, _SetupConfig);
2241
}
2342

2443
#endregion

Modules/Authentication.Web/Concern/WebAuthenticationConcern.cs

+48-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using GenHTTP.Api.Content;
2+
using GenHTTP.Api.Content.Authentication;
23
using GenHTTP.Api.Protocol;
34
using GenHTTP.Api.Routing;
45
using GenHTTP.Modules.Basics;
6+
using Microsoft.AspNetCore.Razor.Language.Intermediate;
57
using System;
68
using System.Collections.Generic;
79
using System.Threading.Tasks;
@@ -18,6 +20,10 @@ public sealed class WebAuthenticationConcern : IConcern, IRootPathAppender, IHan
1820

1921
public IHandler Parent { get; }
2022

23+
private bool AllowAnonymous { get; }
24+
25+
private SessionConfig SessionConfig { get; }
26+
2127
private SetupConfig? SetupConfig { get; }
2228

2329
private IHandler? SetupHandler { get; }
@@ -26,12 +32,15 @@ public sealed class WebAuthenticationConcern : IConcern, IRootPathAppender, IHan
2632

2733
#region Initialization
2834

29-
public WebAuthenticationConcern(IHandler parent, Func<IHandler, IHandler> contentFactory,
30-
SetupConfig? setupConfig)
35+
public WebAuthenticationConcern(IHandler parent, Func<IHandler, IHandler> contentFactory, bool allowAnonymous,
36+
SessionConfig sessionConfig, SetupConfig? setupConfig)
3137
{
3238
Parent = parent;
3339
Content = contentFactory(this);
3440

41+
AllowAnonymous = allowAnonymous;
42+
SessionConfig = sessionConfig;
43+
3544
SetupConfig = setupConfig;
3645
SetupHandler = setupConfig?.Handler.Build(this);
3746
}
@@ -54,6 +63,7 @@ public WebAuthenticationConcern(IHandler parent, Func<IHandler, IHandler> conten
5463
{
5564
if (segment?.Value != SetupConfig.Route)
5665
{
66+
// enforce setup wizard
5767
return await Redirect.To("{setup}/", true)
5868
.Build(this)
5969
.HandleAsync(request);
@@ -67,9 +77,44 @@ public WebAuthenticationConcern(IHandler parent, Func<IHandler, IHandler> conten
6777
return await SetupHandler.HandleAsync(request);
6878
}
6979
}
80+
else if (segment?.Value == SetupConfig.Route)
81+
{
82+
// do not allow setup to be called again
83+
return await Redirect.To("{web-auth}", true)
84+
.Build(this)
85+
.HandleAsync(request);
86+
}
7087
}
7188

72-
return await Content.HandleAsync(request);
89+
var token = await SessionConfig.ReadToken(request);
90+
91+
if (token != null)
92+
{
93+
var authenticatedUser = await SessionConfig.VerifyToken(token);
94+
95+
if (authenticatedUser != null)
96+
{
97+
// we're logged in
98+
return await Content.HandleAsync(request);
99+
}
100+
}
101+
102+
if (AllowAnonymous)
103+
{
104+
var response = await Content.HandleAsync(request);
105+
106+
if ((response != null) && (token != null))
107+
{
108+
// clear the invalid cookie
109+
SessionConfig.ClearToken(response);
110+
}
111+
112+
return null;
113+
}
114+
115+
// enforce login (todo)
116+
117+
return null;
73118
}
74119

75120
public void Append(PathBuilder path, IRequest request, IHandler? child = null)

Modules/Authentication.Web/GenHTTP.Modules.Authentication.Web.csproj

-4
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,4 @@
5454

5555
</ItemGroup>
5656

57-
<ItemGroup>
58-
<Folder Include="Sessions\" />
59-
</ItemGroup>
60-
6157
</Project>

Modules/Authentication.Web/ISessionNegotiation.cs

-15
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
4+
using GenHTTP.Api.Content.Authentication;
5+
using GenHTTP.Api.Protocol;
6+
7+
namespace GenHTTP.Modules.Authentication.Web
8+
{
9+
10+
public record class SessionConfig
11+
(
12+
13+
Func<IRequest, ValueTask<string?>> ReadToken,
14+
15+
Action<IResponseBuilder, string> WriteToken,
16+
17+
Action<IResponse> ClearToken,
18+
19+
Func<string, ValueTask<IUser?>> VerifyToken
20+
21+
);
22+
23+
public static class SessionHandling
24+
{
25+
private const string COOKIE_NAME = "wa_session";
26+
27+
private const ulong COOKIE_TIMEOUT = 2592000; // 30d
28+
29+
public static SessionConfig BuiltIn(Func<string, ValueTask<IUser?>> verifyToken)
30+
{
31+
return new(ReadToken, WriteToken, ClearToken, verifyToken);
32+
}
33+
34+
public static SessionConfig Custom(Func<IRequest, ValueTask<string?>> readToken, Action<IResponseBuilder, string> writeToken, Action<IResponse> clearToken, Func<string, ValueTask<IUser?>> verifyToken)
35+
{
36+
return new(readToken, writeToken, clearToken, verifyToken);
37+
}
38+
39+
private static ValueTask<string?> ReadToken(IRequest request)
40+
{
41+
if (request.Cookies.TryGetValue(COOKIE_NAME, out var token))
42+
{
43+
return new(token.Value);
44+
}
45+
46+
return new();
47+
}
48+
49+
private static void WriteToken(IResponseBuilder response, string value)
50+
{
51+
response.Cookie(new(COOKIE_NAME, value, COOKIE_TIMEOUT));
52+
}
53+
54+
private static void ClearToken(IResponse response)
55+
{
56+
response.SetCookie(new(COOKIE_NAME, string.Empty, 0));
57+
}
58+
59+
}
60+
61+
}

Playground/Program.cs

+6
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@
1111
performSetup: (req, u, p) => { setupDone = true; return new(SetupResult.Success); }
1212
);
1313

14+
var sessionHandling = SessionHandling.BuiltIn
15+
(
16+
verifyToken: (token) => new()
17+
);
18+
1419
var auth = WebAuthentication.Create()
20+
.SessionHandling(sessionHandling)
1521
.EnableSetup(setup);
1622

1723
Host.Create()

0 commit comments

Comments
 (0)