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 Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
|
||||||
@using LocalModels
|
@using LocalModels
|
||||||
@using Management.Web.Shared.Module.Assignment
|
@using Management.Web.Shared.Module.Assignment
|
||||||
|
@using Management.Web.Shared.Components
|
||||||
|
|
||||||
@inject CanvasService canvas
|
@inject CanvasService canvas
|
||||||
@inject CoursePlanner planner
|
@inject CoursePlanner planner
|
||||||
@inject ProtectedLocalStorage BrowserStorage
|
@inject ICanvasTokenManagement tokenManagement
|
||||||
|
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
private bool showNewFile { get; set; } = false;
|
private bool showNewFile { get; set; } = false;
|
||||||
protected override void OnInitialized()
|
private bool hasCanvasToken { get; set; } = false;
|
||||||
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
planner.StateHasChanged += reload;
|
planner.StateHasChanged += reload;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reload()
|
private void reload()
|
||||||
{
|
{
|
||||||
this.InvokeAsync(this.StateHasChanged);
|
this.InvokeAsync(this.StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
planner.StateHasChanged -= reload;
|
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>
|
<PageTitle>Index</PageTitle>
|
||||||
|
|
||||||
|
@if(!hasCanvasToken)
|
||||||
|
{
|
||||||
|
<ValidateCanvasToken SetToken="SetToken" />
|
||||||
|
}
|
||||||
|
|
||||||
@if(planner.LocalCourse == null)
|
@if(planner.LocalCourse == null)
|
||||||
{
|
{
|
||||||
<CurrentFiles />
|
<CurrentFiles />
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ var builder = WebApplication.CreateBuilder(args);
|
|||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddRazorPages();
|
builder.Services.AddRazorPages();
|
||||||
builder.Services.AddServerSideBlazor();
|
builder.Services.AddServerSideBlazor();
|
||||||
builder.Services.AddSingleton<IWebRequestor, WebRequestor>();
|
builder.Services.AddScoped<ICanvasTokenManagement, BrowserStorageManagement>();
|
||||||
builder.Services.AddSingleton<CanvasService, CanvasService>();
|
builder.Services.AddScoped<IWebRequestor, WebRequestor>();
|
||||||
|
builder.Services.AddScoped<CanvasService, CanvasService>();
|
||||||
builder.Services.AddSingleton<YamlManager>();
|
builder.Services.AddSingleton<YamlManager>();
|
||||||
builder.Services.AddSingleton<CoursePlanner>();
|
builder.Services.AddSingleton<CoursePlanner>();
|
||||||
builder.Services.AddSingleton<AssignmentDragContainer>();
|
builder.Services.AddSingleton<AssignmentDragContainer>();
|
||||||
builder.Services.AddScoped<BrowserStorageManagement>();
|
|
||||||
|
|
||||||
var app = builder.Build();
|
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
|
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
|
||||||
|
|
||||||
@inject CoursePlanner planner
|
@inject CoursePlanner planner
|
||||||
@inject ProtectedLocalStorage BrowserStorage
|
|
||||||
@inject BrowserStorageManagement storage
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private bool showNewModule { get; set; } = false;
|
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;
|
using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
|
||||||
|
|
||||||
public class BrowserStorageManagement
|
public class BrowserStorageManagement : ICanvasTokenManagement
|
||||||
{
|
{
|
||||||
// private string moduleStorageKey = "module storage key";
|
// private string moduleStorageKey = "module storage key";
|
||||||
// private string assignmentStorageKey = "assignment 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 ProtectedLocalStorage storage { get; }
|
||||||
private CanvasService canvas { get; }
|
|
||||||
|
|
||||||
public BrowserStorageManagement(
|
public BrowserStorageManagement(ProtectedLocalStorage BrowserStorage)
|
||||||
CoursePlanner configurationManagement,
|
|
||||||
ProtectedLocalStorage BrowserStorage,
|
|
||||||
CanvasService canvasService
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
planner = configurationManagement;
|
|
||||||
storage = BrowserStorage;
|
storage = BrowserStorage;
|
||||||
canvas = canvasService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// public async Task LoadStoredConfig()
|
public async Task<string?> GetCanvasToken()
|
||||||
// {
|
{
|
||||||
// // var storedModules = await storage.GetAsync<IEnumerable<CourseModule>>(moduleStorageKey);
|
var result = await storage.GetAsync<string>(canvasKey);
|
||||||
// // if (storedModules.Success)
|
if (!result.Success)
|
||||||
// // {
|
return null;
|
||||||
// // planner.Modules =
|
return result.Value;
|
||||||
// // 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);
|
public async Task SaveCanvasToken(string token)
|
||||||
// // if (storedAssignments.Success)
|
{
|
||||||
// // {
|
await storage.SetAsync(canvasKey, token);
|
||||||
// // 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);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
public class WebRequestor : IWebRequestor
|
||||||
{
|
{
|
||||||
private const string BaseUrl = "https://snow.instructure.com/api/v1/";
|
private const string BaseUrl = "https://snow.instructure.com/api/v1/";
|
||||||
private string token;
|
private bool tokenSet = false;
|
||||||
private RestClient client;
|
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 = 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)
|
public async Task<(T[]?, RestResponse)> GetManyAsync<T>(RestRequest request)
|
||||||
{
|
{
|
||||||
|
await EnsureCanvasTokenSet();
|
||||||
var response = await client.ExecuteGetAsync(request);
|
var response = await client.ExecuteGetAsync(request);
|
||||||
return (Deserialize<T[]>(response), response);
|
return (Deserialize<T[]>(response), response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(T?, RestResponse)> GetAsync<T>(RestRequest request)
|
public async Task<(T?, RestResponse)> GetAsync<T>(RestRequest request)
|
||||||
{
|
{
|
||||||
|
await EnsureCanvasTokenSet();
|
||||||
var response = await client.ExecuteGetAsync(request);
|
var response = await client.ExecuteGetAsync(request);
|
||||||
return (Deserialize<T>(response), response);
|
return (Deserialize<T>(response), response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RestResponse> PostAsync(RestRequest request)
|
public async Task<RestResponse> PostAsync(RestRequest request)
|
||||||
{
|
{
|
||||||
|
await EnsureCanvasTokenSet();
|
||||||
var response = await client.ExecutePostAsync(request);
|
var response = await client.ExecutePostAsync(request);
|
||||||
if (!response.IsSuccessful)
|
if (!response.IsSuccessful)
|
||||||
{
|
{
|
||||||
@@ -42,6 +56,7 @@ public class WebRequestor : IWebRequestor
|
|||||||
|
|
||||||
public async Task<(T?, RestResponse)> PostAsync<T>(RestRequest request)
|
public async Task<(T?, RestResponse)> PostAsync<T>(RestRequest request)
|
||||||
{
|
{
|
||||||
|
await EnsureCanvasTokenSet();
|
||||||
var response = await client.ExecutePostAsync(request);
|
var response = await client.ExecutePostAsync(request);
|
||||||
return (Deserialize<T>(response), response);
|
return (Deserialize<T>(response), response);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user