moved access token to browser based

This commit is contained in:
2023-07-26 22:10:10 -06:00
parent 87fc062dfb
commit b03e81caf1
12 changed files with 118 additions and 240 deletions

View File

@@ -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;
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();
}
}
<PageTitle>Index</PageTitle>
@if(!hasCanvasToken)
{
<ValidateCanvasToken SetToken="SetToken" />
}
@if(planner.LocalCourse == null)
{
<CurrentFiles />

View File

@@ -19,12 +19,12 @@ var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<IWebRequestor, WebRequestor>();
builder.Services.AddSingleton<CanvasService, CanvasService>();
builder.Services.AddScoped<ICanvasTokenManagement, BrowserStorageManagement>();
builder.Services.AddScoped<IWebRequestor, WebRequestor>();
builder.Services.AddScoped<CanvasService, CanvasService>();
builder.Services.AddSingleton<YamlManager>();
builder.Services.AddSingleton<CoursePlanner>();
builder.Services.AddSingleton<AssignmentDragContainer>();
builder.Services.AddScoped<BrowserStorageManagement>();
var app = builder.Build();

View File

@@ -0,0 +1,49 @@
@code
{
[Parameter, EditorRequired]
public Func<string,Task> 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();
}
}
<Modal @ref="modal">
<Title>
<h3>Canvas Token</h3>
</Title>
<Body>
<div>
<p>
Please input your canvas token to enable canvas integration
</p>
<p>
We only store the token encrypted in your browser. We do not store the token on our servers.
</p>
<p>
You can get your canvas token <a href="https://snow.instructure.com/profile/settings">here</a>
</p>
<form
onsubmit:preventDefault="true"
@onsubmit="async () => await SetToken(tokenInput)"
>
<input
type="text"
class="form-control"
@bind="tokenInput"
@bind:event="oninput"
/>
</form>
</div>
</Body>
<Footer>
</Footer>
</Modal>

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -1,30 +0,0 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">Management.Web</a>
<button
title="Navigation menu"
class="navbar-toggler"
@onclick="ToggleNavMenu"
>
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div>
<div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link px-3" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</div>
</nav>
</div>
@code {
private bool collapseNavMenu = true; private string? NavMenuCssClass =>
collapseNavMenu ? "collapse" : null; private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}

View File

@@ -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;
}
}

View File

@@ -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<IEnumerable<CourseModule>>(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");
// // }
// // var storedAssignments = await storage.GetAsync<IEnumerable<CourseModule>>(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<ulong>(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<string?> GetCanvasToken()
{
var result = await storage.GetAsync<string>(canvasKey);
if (!result.Success)
return null;
return result.Value;
}
public async Task SaveCanvasToken(string token)
{
await storage.SetAsync(canvasKey, token);
}
}

View File

@@ -0,0 +1,5 @@
public interface ICanvasTokenManagement
{
Task<string?> GetCanvasToken();
Task SaveCanvasToken(string token);
}

View File

@@ -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<T>(RestRequest request)
{
await EnsureCanvasTokenSet();
var response = await client.ExecuteGetAsync(request);
return (Deserialize<T[]>(response), response);
}
public async Task<(T?, RestResponse)> GetAsync<T>(RestRequest request)
{
await EnsureCanvasTokenSet();
var response = await client.ExecuteGetAsync(request);
return (Deserialize<T>(response), response);
}
public async Task<RestResponse> 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<T>(RestRequest request)
{
await EnsureCanvasTokenSet();
var response = await client.ExecutePostAsync(request);
return (Deserialize<T>(response), response);
}