From b03e81caf155b15425fc1a87e3f0b3addddd31ba Mon Sep 17 00:00:00 2001 From: Alex Mickelson Date: Wed, 26 Jul 2023 22:10:10 -0600 Subject: [PATCH] moved access token to browser based --- Management.Web/Pages/Index.razor | 28 ++++++- Management.Web/Program.cs | 6 +- .../Shared/{ => Components}/Spinner.razor | 0 .../Shared/{ => Components}/Spinner.razor.css | 0 .../Components/ValidateCanvasToken.razor | 49 +++++++++++++ Management.Web/Shared/MainLayout.razor.css | 70 ------------------ Management.Web/Shared/Module/Modules.razor | 2 - Management.Web/Shared/NavMenu.razor | 30 -------- Management.Web/Shared/NavMenu.razor.css | 68 ----------------- Management.Web/Utils/StorageManagement.cs | 73 ++++--------------- Management/Services/ICanvasTokenManagement.cs | 5 ++ Management/Services/WebRequestor.cs | 27 +++++-- 12 files changed, 118 insertions(+), 240 deletions(-) rename Management.Web/Shared/{ => Components}/Spinner.razor (100%) rename Management.Web/Shared/{ => Components}/Spinner.razor.css (100%) create mode 100644 Management.Web/Shared/Components/ValidateCanvasToken.razor delete mode 100644 Management.Web/Shared/MainLayout.razor.css delete mode 100644 Management.Web/Shared/NavMenu.razor delete mode 100644 Management.Web/Shared/NavMenu.razor.css create mode 100644 Management/Services/ICanvasTokenManagement.cs diff --git a/Management.Web/Pages/Index.razor b/Management.Web/Pages/Index.razor index db7ef23..bc70b8a 100644 --- a/Management.Web/Pages/Index.razor +++ b/Management.Web/Pages/Index.razor @@ -7,30 +7,54 @@ @using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage @using LocalModels @using Management.Web.Shared.Module.Assignment +@using Management.Web.Shared.Components @inject CanvasService canvas @inject CoursePlanner planner -@inject ProtectedLocalStorage BrowserStorage +@inject ICanvasTokenManagement tokenManagement + @code { private bool showNewFile { get; set; } = false; -protected override void OnInitialized() + private bool hasCanvasToken { get; set; } = false; + protected override void OnInitialized() { planner.StateHasChanged += reload; } + private void reload() { this.InvokeAsync(this.StateHasChanged); } + public void Dispose() { planner.StateHasChanged -= reload; } + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if(firstRender) + { + hasCanvasToken = await tokenManagement.GetCanvasToken() != null; + StateHasChanged(); + } + } + private async Task SetToken(string newToken) + { + await tokenManagement.SaveCanvasToken(newToken); + hasCanvasToken = true; + StateHasChanged(); + } } Index +@if(!hasCanvasToken) +{ + +} + @if(planner.LocalCourse == null) { diff --git a/Management.Web/Program.cs b/Management.Web/Program.cs index 2994758..f7b4165 100644 --- a/Management.Web/Program.cs +++ b/Management.Web/Program.cs @@ -19,12 +19,12 @@ var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); -builder.Services.AddScoped(); var app = builder.Build(); diff --git a/Management.Web/Shared/Spinner.razor b/Management.Web/Shared/Components/Spinner.razor similarity index 100% rename from Management.Web/Shared/Spinner.razor rename to Management.Web/Shared/Components/Spinner.razor diff --git a/Management.Web/Shared/Spinner.razor.css b/Management.Web/Shared/Components/Spinner.razor.css similarity index 100% rename from Management.Web/Shared/Spinner.razor.css rename to Management.Web/Shared/Components/Spinner.razor.css diff --git a/Management.Web/Shared/Components/ValidateCanvasToken.razor b/Management.Web/Shared/Components/ValidateCanvasToken.razor new file mode 100644 index 0000000..ab60144 --- /dev/null +++ b/Management.Web/Shared/Components/ValidateCanvasToken.razor @@ -0,0 +1,49 @@ + +@code +{ + [Parameter, EditorRequired] + public Func SetToken { get; set; } = default!; + private Modal modal { get; set; } = default!; + private string tokenInput { get; set; } = ""; + + protected override void OnAfterRender(bool firstRender) + { + if(firstRender) + modal.Show(); + } + + +} + + + + <h3>Canvas Token</h3> + + +
+

+ Please input your canvas token to enable canvas integration +

+

+ We only store the token encrypted in your browser. We do not store the token on our servers. +

+

+ You can get your canvas token here +

+
+ +
+
+ +
+
+
+ diff --git a/Management.Web/Shared/MainLayout.razor.css b/Management.Web/Shared/MainLayout.razor.css deleted file mode 100644 index 699f17c..0000000 --- a/Management.Web/Shared/MainLayout.razor.css +++ /dev/null @@ -1,70 +0,0 @@ -.page { - position: relative; - display: flex; - flex-direction: column; -} - -main { - flex: 1; -} - -.sidebar { - background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); -} - -.top-row { - background-color: #f7f7f7; - border-bottom: 1px solid #d6d5d5; - justify-content: flex-end; - height: 3.5rem; - display: flex; - align-items: center; -} - - .top-row ::deep a, .top-row .btn-link { - white-space: nowrap; - margin-left: 1.5rem; - } - - .top-row a:first-child { - overflow: hidden; - text-overflow: ellipsis; - } - -@media (max-width: 640.98px) { - .top-row:not(.auth) { - display: none; - } - - .top-row.auth { - justify-content: space-between; - } - - .top-row a, .top-row .btn-link { - margin-left: 0; - } -} - -@media (min-width: 641px) { - .page { - flex-direction: row; - } - - .sidebar { - width: 250px; - height: 100vh; - position: sticky; - top: 0; - } - - .top-row { - position: sticky; - top: 0; - z-index: 1; - } - - .top-row, article { - padding-left: 2rem !important; - padding-right: 1.5rem !important; - } -} diff --git a/Management.Web/Shared/Module/Modules.razor b/Management.Web/Shared/Module/Modules.razor index 0977836..c6b39c9 100644 --- a/Management.Web/Shared/Module/Modules.razor +++ b/Management.Web/Shared/Module/Modules.razor @@ -3,8 +3,6 @@ @using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage @inject CoursePlanner planner -@inject ProtectedLocalStorage BrowserStorage -@inject BrowserStorageManagement storage @code { private bool showNewModule { get; set; } = false; diff --git a/Management.Web/Shared/NavMenu.razor b/Management.Web/Shared/NavMenu.razor deleted file mode 100644 index b7a2b2a..0000000 --- a/Management.Web/Shared/NavMenu.razor +++ /dev/null @@ -1,30 +0,0 @@ - - - - -@code { - private bool collapseNavMenu = true; private string? NavMenuCssClass => - collapseNavMenu ? "collapse" : null; private void ToggleNavMenu() - { - collapseNavMenu = !collapseNavMenu; - } -} diff --git a/Management.Web/Shared/NavMenu.razor.css b/Management.Web/Shared/NavMenu.razor.css deleted file mode 100644 index 77a8500..0000000 --- a/Management.Web/Shared/NavMenu.razor.css +++ /dev/null @@ -1,68 +0,0 @@ -.navbar-toggler { - background-color: rgba(255, 255, 255, 0.1); -} - -.top-row { - height: 3.5rem; - background-color: rgba(0,0,0,0.4); -} - -.navbar-brand { - font-size: 1.1rem; -} - -.oi { - width: 2rem; - font-size: 1.1rem; - vertical-align: text-top; - top: -2px; -} - -.nav-item { - font-size: 0.9rem; - padding-bottom: 0.5rem; -} - - .nav-item:first-of-type { - padding-top: 1rem; - } - - .nav-item:last-of-type { - padding-bottom: 1rem; - } - - .nav-item ::deep a { - color: #d7d7d7; - border-radius: 4px; - height: 3rem; - display: flex; - align-items: center; - line-height: 3rem; - } - -.nav-item ::deep a.active { - background-color: rgba(255,255,255,0.25); - color: white; -} - -.nav-item ::deep a:hover { - background-color: rgba(255,255,255,0.1); - color: white; -} - -@media (min-width: 641px) { - .navbar-toggler { - display: none; - } - - .collapse { - /* Never collapse the sidebar for wide screens */ - display: block; - } - - .nav-scrollable { - /* Allow sidebar to scroll for tall menus */ - height: calc(100vh - 3.5rem); - overflow-y: auto; - } -} diff --git a/Management.Web/Utils/StorageManagement.cs b/Management.Web/Utils/StorageManagement.cs index c4ff1a0..c1e83a2 100644 --- a/Management.Web/Utils/StorageManagement.cs +++ b/Management.Web/Utils/StorageManagement.cs @@ -1,73 +1,28 @@ using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage; -public class BrowserStorageManagement +public class BrowserStorageManagement : ICanvasTokenManagement { // private string moduleStorageKey = "module storage key"; // private string assignmentStorageKey = "assignment storage key"; - // private string courseIdKey = "course id storage key"; + private string canvasKey = "canvas key"; - private CoursePlanner planner { get; } private ProtectedLocalStorage storage { get; } - private CanvasService canvas { get; } - public BrowserStorageManagement( - CoursePlanner configurationManagement, - ProtectedLocalStorage BrowserStorage, - CanvasService canvasService - ) + public BrowserStorageManagement(ProtectedLocalStorage BrowserStorage) { - planner = configurationManagement; storage = BrowserStorage; - canvas = canvasService; } - // public async Task LoadStoredConfig() - // { - // // var storedModules = await storage.GetAsync>(moduleStorageKey); - // // if (storedModules.Success) - // // { - // // planner.Modules = - // // storedModules.Value - // // ?? throw new Exception("stored modules was null, it shouldn't have been"); - // // } - // // else - // // { - // // Console.WriteLine("no stored modules"); - // // } + public async Task GetCanvasToken() + { + var result = await storage.GetAsync(canvasKey); + if (!result.Success) + return null; + return result.Value; + } - // // var storedAssignments = await storage.GetAsync>(assignmentStorageKey); - // // if (storedAssignments.Success) - // // { - // // planner.Modules = - // // storedAssignments.Value - // // ?? throw new Exception("stored assignments are null, it shouldn't have been"); - // // } - // // else - // // { - // // Console.WriteLine("no stored assignments"); - // // } - - // // var storedCourseId = await storage.GetAsync(courseIdKey); - // // if (storedCourseId.Success) - // // { - // // // var courses = - // // planner.Course = await canvas.GetCourse(storedCourseId.Value); - // // planner.Modules = await canvas.GetModules(planner.Course.Id); - // // } - // // else - // // { - // // Console.WriteLine("no stored assignments"); - // // } - // } - - // public async Task Save() - // { - // // await storage.SetAsync(moduleStorageKey, planner.Modules); - // // await storage.SetAsync(assignmentStorageKey, planner.Assignments); - - // // if (planner.Course != null) - // // await storage.SetAsync(courseIdKey, planner.Course.Id); - // // else - // // await storage.DeleteAsync(courseIdKey); - // } + public async Task SaveCanvasToken(string token) + { + await storage.SetAsync(canvasKey, token); + } } diff --git a/Management/Services/ICanvasTokenManagement.cs b/Management/Services/ICanvasTokenManagement.cs new file mode 100644 index 0000000..a33e182 --- /dev/null +++ b/Management/Services/ICanvasTokenManagement.cs @@ -0,0 +1,5 @@ +public interface ICanvasTokenManagement +{ + Task GetCanvasToken(); + Task SaveCanvasToken(string token); +} \ No newline at end of file diff --git a/Management/Services/WebRequestor.cs b/Management/Services/WebRequestor.cs index f8a3620..eec913b 100644 --- a/Management/Services/WebRequestor.cs +++ b/Management/Services/WebRequestor.cs @@ -3,32 +3,46 @@ using RestSharp; public class WebRequestor : IWebRequestor { private const string BaseUrl = "https://snow.instructure.com/api/v1/"; - private string token; + private bool tokenSet = false; private RestClient client; - public WebRequestor() + private ICanvasTokenManagement tokenManagement { get; } + + public WebRequestor(ICanvasTokenManagement tokenManagement) { - token = - Environment.GetEnvironmentVariable("CANVAS_TOKEN") - ?? throw new Exception("CANVAS_TOKEN not in environment"); client = new RestClient(BaseUrl); - client.AddDefaultHeader("Authorization", $"Bearer {token}"); + this.tokenManagement = tokenManagement; } + private async Task EnsureCanvasTokenSet() + { + if (tokenSet) + return; + + var newToken = await tokenManagement.GetCanvasToken(); + if(newToken == null) + throw new Exception("cannot request canvas, no token in storage"); + + client.AddDefaultHeader("Authorization", $"Bearer {newToken}"); + tokenSet = true; + } public async Task<(T[]?, RestResponse)> GetManyAsync(RestRequest request) { + await EnsureCanvasTokenSet(); var response = await client.ExecuteGetAsync(request); return (Deserialize(response), response); } public async Task<(T?, RestResponse)> GetAsync(RestRequest request) { + await EnsureCanvasTokenSet(); var response = await client.ExecuteGetAsync(request); return (Deserialize(response), response); } public async Task PostAsync(RestRequest request) { + await EnsureCanvasTokenSet(); var response = await client.ExecutePostAsync(request); if (!response.IsSuccessful) { @@ -42,6 +56,7 @@ public class WebRequestor : IWebRequestor public async Task<(T?, RestResponse)> PostAsync(RestRequest request) { + await EnsureCanvasTokenSet(); var response = await client.ExecutePostAsync(request); return (Deserialize(response), response); }