mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-25 23:28:33 -06:00
moved access token to browser based
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
<PageTitle>Index</PageTitle>
|
||||
|
||||
@if(!hasCanvasToken)
|
||||
{
|
||||
<ValidateCanvasToken SetToken="SetToken" />
|
||||
}
|
||||
|
||||
@if(planner.LocalCourse == null)
|
||||
{
|
||||
<CurrentFiles />
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
49
Management.Web/Shared/Components/ValidateCanvasToken.razor
Normal file
49
Management.Web/Shared/Components/ValidateCanvasToken.razor
Normal 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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
// // }
|
||||
public async Task<string?> GetCanvasToken()
|
||||
{
|
||||
var result = await storage.GetAsync<string>(canvasKey);
|
||||
if (!result.Success)
|
||||
return null;
|
||||
return result.Value;
|
||||
}
|
||||
|
||||
// // 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 SaveCanvasToken(string token)
|
||||
{
|
||||
await storage.SetAsync(canvasKey, token);
|
||||
}
|
||||
}
|
||||
|
||||
5
Management/Services/ICanvasTokenManagement.cs
Normal file
5
Management/Services/ICanvasTokenManagement.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
public interface ICanvasTokenManagement
|
||||
{
|
||||
Task<string?> GetCanvasToken();
|
||||
Task SaveCanvasToken(string token);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user