mirror of
https://github.com/Sarsoo/Spotify.NET.git
synced 2024-12-23 22:56:25 +00:00
First draft of SpotifyAPI.Web.Auth
This commit is contained in:
parent
255bbd5c2f
commit
ff9d03ffb0
20
SpotifyAPI.Web.Auth/AuthException.cs
Normal file
20
SpotifyAPI.Web.Auth/AuthException.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
namespace SpotifyAPI.Web.Auth
|
||||||
|
{
|
||||||
|
[System.Serializable]
|
||||||
|
public class AuthException : System.Exception
|
||||||
|
{
|
||||||
|
public AuthException(string error, string state)
|
||||||
|
{
|
||||||
|
Error = error;
|
||||||
|
State = state;
|
||||||
|
}
|
||||||
|
public AuthException(string message) : base(message) { }
|
||||||
|
public AuthException(string message, System.Exception inner) : base(message, inner) { }
|
||||||
|
protected AuthException(
|
||||||
|
System.Runtime.Serialization.SerializationInfo info,
|
||||||
|
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
|
||||||
|
|
||||||
|
public string Error { get; set; }
|
||||||
|
public string State { get; set; }
|
||||||
|
}
|
||||||
|
}
|
26
SpotifyAPI.Web.Auth/BrowserUtil.cs
Normal file
26
SpotifyAPI.Web.Auth/BrowserUtil.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SpotifyAPI.Web.Auth
|
||||||
|
{
|
||||||
|
public static class BrowserUtil
|
||||||
|
{
|
||||||
|
public static void Open(Uri uri)
|
||||||
|
{
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
var uriStr = uri.ToString().Replace("&", "^&");
|
||||||
|
Process.Start(new ProcessStartInfo($"cmd", $"/c start {uriStr}"));
|
||||||
|
}
|
||||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||||
|
{
|
||||||
|
Process.Start("xdg-open", uri.ToString());
|
||||||
|
}
|
||||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||||
|
{
|
||||||
|
Process.Start("open", uri.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
120
SpotifyAPI.Web.Auth/EmbedIOAuthServer.cs
Normal file
120
SpotifyAPI.Web.Auth/EmbedIOAuthServer.cs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Web;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using EmbedIO;
|
||||||
|
using EmbedIO.Actions;
|
||||||
|
|
||||||
|
namespace SpotifyAPI.Web.Auth
|
||||||
|
{
|
||||||
|
public class EmbedIOAuthServer : IAuthServer
|
||||||
|
{
|
||||||
|
public event Func<object, AuthorizationCodeResponse, Task> AuthorizationCodeReceived;
|
||||||
|
public event Func<object, ImplictGrantResponse, Task> ImplictGrantReceived;
|
||||||
|
|
||||||
|
private const string CallbackPath = "/";
|
||||||
|
private const string DefaultResourcePath = "SpotifyAPI.Web.Auth.Resources.DefaultHTML";
|
||||||
|
|
||||||
|
private CancellationTokenSource _cancelTokenSource;
|
||||||
|
private readonly WebServer _webServer;
|
||||||
|
|
||||||
|
public EmbedIOAuthServer(Uri baseUri, int port, string resourcePath = DefaultResourcePath)
|
||||||
|
{
|
||||||
|
Ensure.ArgumentNotNull(baseUri, nameof(baseUri));
|
||||||
|
|
||||||
|
BaseUri = baseUri;
|
||||||
|
Port = port;
|
||||||
|
|
||||||
|
_webServer = new WebServer(port)
|
||||||
|
.WithModule(new ActionModule("/", HttpVerbs.Post, (ctx) =>
|
||||||
|
{
|
||||||
|
var query = ctx.Request.QueryString;
|
||||||
|
if (query["error"] != null)
|
||||||
|
{
|
||||||
|
throw new AuthException(query["error"], query["state"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var requestType = query.Get("request_type");
|
||||||
|
if (requestType == "token")
|
||||||
|
{
|
||||||
|
ImplictGrantReceived?.Invoke(this, new ImplictGrantResponse(
|
||||||
|
query["access_token"], query["token_type"], int.Parse(query["expires_in"])
|
||||||
|
)
|
||||||
|
{
|
||||||
|
State = query["state"]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (requestType == "code")
|
||||||
|
{
|
||||||
|
AuthorizationCodeReceived?.Invoke(this, new AuthorizationCodeResponse(query["code"])
|
||||||
|
{
|
||||||
|
State = query["state"]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.SendStringAsync("OK", "text/plain", Encoding.UTF8);
|
||||||
|
}))
|
||||||
|
.WithEmbeddedResources("/", Assembly.GetExecutingAssembly(), resourcePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri BaseUri { get; }
|
||||||
|
public Uri RedirectUri { get => new Uri(BaseUri, CallbackPath); }
|
||||||
|
public int Port { get; }
|
||||||
|
|
||||||
|
public Task Start()
|
||||||
|
{
|
||||||
|
_cancelTokenSource = new CancellationTokenSource();
|
||||||
|
_webServer.Start(_cancelTokenSource.Token);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Stop()
|
||||||
|
{
|
||||||
|
_cancelTokenSource.Cancel();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri BuildLoginUri(LoginRequest request)
|
||||||
|
{
|
||||||
|
Ensure.ArgumentNotNull(request, nameof(request));
|
||||||
|
|
||||||
|
var callbackUri = new Uri(BaseUri, CallbackPath);
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder(SpotifyUrls.Authorize.ToString());
|
||||||
|
builder.Append($"?client_id={request.ClientId}");
|
||||||
|
builder.Append($"&response_type={request.ResponseTypeParam.ToString().ToLower()}");
|
||||||
|
builder.Append($"&redirect_uri={HttpUtility.UrlEncode(callbackUri.ToString())}");
|
||||||
|
if (!string.IsNullOrEmpty(request.State))
|
||||||
|
{
|
||||||
|
builder.Append($"&state={HttpUtility.UrlEncode(request.State)}");
|
||||||
|
}
|
||||||
|
if (request.Scope != null)
|
||||||
|
{
|
||||||
|
builder.Append($"&scope={HttpUtility.UrlEncode(string.Join(" ", request.Scope))}");
|
||||||
|
}
|
||||||
|
if (request.ShowDialog != null)
|
||||||
|
{
|
||||||
|
builder.Append($"&show_dialog={request.ShowDialog.Value}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Uri(builder.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_webServer?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
SpotifyAPI.Web.Auth/IAuthServer.cs
Normal file
19
SpotifyAPI.Web.Auth/IAuthServer.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SpotifyAPI.Web.Auth
|
||||||
|
{
|
||||||
|
public interface IAuthServer : IDisposable
|
||||||
|
{
|
||||||
|
event Func<object, AuthorizationCodeResponse, Task> AuthorizationCodeReceived;
|
||||||
|
|
||||||
|
event Func<object, ImplictGrantResponse, Task> ImplictGrantReceived;
|
||||||
|
|
||||||
|
Task Start();
|
||||||
|
Task Stop();
|
||||||
|
|
||||||
|
Uri BuildLoginUri(LoginRequest request);
|
||||||
|
|
||||||
|
Uri RedirectUri { get; }
|
||||||
|
}
|
||||||
|
}
|
26
SpotifyAPI.Web.Auth/Models/Request/LoginRequest.cs
Normal file
26
SpotifyAPI.Web.Auth/Models/Request/LoginRequest.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
namespace SpotifyAPI.Web.Auth
|
||||||
|
{
|
||||||
|
public class LoginRequest
|
||||||
|
{
|
||||||
|
public LoginRequest(string clientId, ResponseType responseType)
|
||||||
|
{
|
||||||
|
Ensure.ArgumentNotNullOrEmptyString(clientId, nameof(clientId));
|
||||||
|
|
||||||
|
ClientId = clientId;
|
||||||
|
ResponseTypeParam = responseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseType ResponseTypeParam { get; }
|
||||||
|
public string ClientId { get; }
|
||||||
|
public string State { get; set; }
|
||||||
|
public ICollection<string> Scope { get; set; }
|
||||||
|
public bool? ShowDialog { get; set; }
|
||||||
|
|
||||||
|
public enum ResponseType
|
||||||
|
{
|
||||||
|
Code,
|
||||||
|
Token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
namespace SpotifyAPI.Web.Auth
|
||||||
|
{
|
||||||
|
public class AuthorizationCodeResponse
|
||||||
|
{
|
||||||
|
public AuthorizationCodeResponse(string code)
|
||||||
|
{
|
||||||
|
Ensure.ArgumentNotNullOrEmptyString(code, nameof(code));
|
||||||
|
|
||||||
|
Code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Code { get; set; }
|
||||||
|
public string State { get; set; }
|
||||||
|
}
|
||||||
|
}
|
30
SpotifyAPI.Web.Auth/Models/Response/ImplicitGrantResponse.cs
Normal file
30
SpotifyAPI.Web.Auth/Models/Response/ImplicitGrantResponse.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SpotifyAPI.Web.Auth
|
||||||
|
{
|
||||||
|
public class ImplictGrantResponse
|
||||||
|
{
|
||||||
|
public ImplictGrantResponse(string accessToken, string tokenType, int expiresIn)
|
||||||
|
{
|
||||||
|
Ensure.ArgumentNotNullOrEmptyString(accessToken, nameof(accessToken));
|
||||||
|
Ensure.ArgumentNotNullOrEmptyString(tokenType, nameof(tokenType));
|
||||||
|
|
||||||
|
AccessToken = accessToken;
|
||||||
|
TokenType = tokenType;
|
||||||
|
ExpiresIn = expiresIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string AccessToken { get; set; }
|
||||||
|
public string TokenType { get; set; }
|
||||||
|
public int ExpiresIn { get; set; }
|
||||||
|
public string State { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Auto-Initalized to UTC Now
|
||||||
|
/// </summary>
|
||||||
|
/// <value></value>
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
public bool IsExpired { get => CreatedAt.AddSeconds(ExpiresIn) <= DateTime.UtcNow; }
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
Before Width: | Height: | Size: 38 KiB |
Binary file not shown.
Before Width: | Height: | Size: 16 KiB |
@ -1,77 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title></title>
|
|
||||||
<link rel="stylesheet" href="css/bulma.min.css"/>
|
|
||||||
<style>
|
|
||||||
p {
|
|
||||||
font-size: 1.0rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.demo-image {
|
|
||||||
margin: 30px;
|
|
||||||
border: 20px solid #31BD5B;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<div class="notification has-text-centered">
|
|
||||||
<h1 class="title is-1">Spotify Authentication</h1>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<h2 class="title is-2">Introduction</h2>
|
|
||||||
<!-- <p>
|
|
||||||
In order to use this app, you will need to follow the steps below.
|
|
||||||
You will create a Spotify Developer App, which allows applications like this to securely access your spotify data, like playlists or your currently playing track.
|
|
||||||
<p class="notification is-warning">
|
|
||||||
If this page looks similar, you may already have a Spotify Developer App created. In this case, you can skip to the bottom and input your <code>client_id</code> and <code>client_secret</code>
|
|
||||||
</p>
|
|
||||||
</p>
|
|
||||||
<h2 class="title is-2">1. Login at your Developer Dashboard</h2>
|
|
||||||
<p>
|
|
||||||
Visit <a href="https://developer.spotify.com/dashboard/" rel="nofollow noreferer" target="_blank">https://developer.spotify.com/dashboard/</a> and login with your Spotify Account.
|
|
||||||
<img class="demo-image" src="images/1.png" width="700" alt=""/>
|
|
||||||
</p>
|
|
||||||
<h2 class="title is-2">2. Create a new ClientID</h2>
|
|
||||||
<p>
|
|
||||||
Visit <a href="https://developer.spotify.com/dashboard/" rel="nofollow noreferer" target="_blank">https://developer.spotify.com/dashboard/</a> and login with your Spotify Account.
|
|
||||||
<img class="demo-image" src="images/2.png" width="700" alt=""/>
|
|
||||||
</p>-->
|
|
||||||
<form action="/" method="post" style="margin-bottom: 20px">
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="clientId">Client ID</label>
|
|
||||||
<div class="control">
|
|
||||||
<input class="input" type="text" placeholder="Client ID" name="clientId" id="clientId">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="secretId">Secret ID</label>
|
|
||||||
<div class="control">
|
|
||||||
<input class="input" type="password" placeholder="Secret ID" name="secretId" id="secretId">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input class="input is-hidden" hidden name="state" id="state"/>
|
|
||||||
<div class="field is-grouped">
|
|
||||||
<div class="control">
|
|
||||||
<button class="button is-link">Submit</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
const hash = window.location.hash.split("#")[1];
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
|
||||||
const input = document.getElementById("state");
|
|
||||||
input.value = hash;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
BIN
SpotifyAPI.Web.Auth/Resources/DefaultHTML/favicon.ico
Normal file
BIN
SpotifyAPI.Web.Auth/Resources/DefaultHTML/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
38
SpotifyAPI.Web.Auth/Resources/DefaultHTML/index.html
Normal file
38
SpotifyAPI.Web.Auth/Resources/DefaultHTML/index.html
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset='utf-8'>
|
||||||
|
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
|
||||||
|
<title>Spotify Authorization</title>
|
||||||
|
<meta name='viewport' content='width=device-width, initial-scale=1'>
|
||||||
|
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
|
||||||
|
<link href="/main.css" rel="stylesheet">
|
||||||
|
<script src="/main.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="flex justify-center flex-wrap logo">
|
||||||
|
<div class="w-1/8">
|
||||||
|
<img src="logo.svg" width="120" height="120" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h1 class="text-4xl">Success!</h1>
|
||||||
|
<p class="text-xl mx-2">
|
||||||
|
Spotify Authorization was successful. You can close this tab and go back to your app.
|
||||||
|
</p>
|
||||||
|
<div class="text-center py-4 lg:px-4 my-6">
|
||||||
|
<div class="p-2 bg-teal-800 items-center text-teal-100 leading-none lg:rounded-full flex lg:inline-flex"
|
||||||
|
role="alert">
|
||||||
|
<span class="flex rounded-full bg-teal-500 uppercase px-2 py-1 text-xs font-bold mr-3">Tip</span>
|
||||||
|
<span class="font-semibold mr-2 text-left flex-auto">
|
||||||
|
If the app does not detect the authorization, make sure you use one of the following supported Browsers:
|
||||||
|
<b>Chrome</b>, <b>Edge</b> or <b>Firefox</b>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
9
SpotifyAPI.Web.Auth/Resources/DefaultHTML/logo.svg
Normal file
9
SpotifyAPI.Web.Auth/Resources/DefaultHTML/logo.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 51 KiB |
22
SpotifyAPI.Web.Auth/Resources/DefaultHTML/main.css
Normal file
22
SpotifyAPI.Web.Auth/Resources/DefaultHTML/main.css
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
html,
|
||||||
|
body {
|
||||||
|
width : 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color : #f5f6fa;
|
||||||
|
background-color : #353b48;
|
||||||
|
width : 100%;
|
||||||
|
height : 100%;
|
||||||
|
background-attachment: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}
|
43
SpotifyAPI.Web.Auth/Resources/DefaultHTML/main.js
Normal file
43
SpotifyAPI.Web.Auth/Resources/DefaultHTML/main.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
function getUrlParams(hash, start) {
|
||||||
|
const hashes = hash.slice(hash.indexOf(start) + 1).split('&')
|
||||||
|
|
||||||
|
if (!hashes || hashes.length === 0 || hashes[0] === "") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {}
|
||||||
|
hashes.map(hash => {
|
||||||
|
const [key, val] = hash.split('=')
|
||||||
|
params[key] = decodeURIComponent(val)
|
||||||
|
})
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleImplicitGrant() {
|
||||||
|
const params = getUrlParams(window.location.hash, '#');
|
||||||
|
if (!params) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
params.request_type = "token";
|
||||||
|
|
||||||
|
console.log("Sent request_type token to server", params);
|
||||||
|
fetch('?' + new URLSearchParams(params).toString(), {
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
handleImplicitGrant();
|
||||||
|
|
||||||
|
function handleAuthenticationCode() {
|
||||||
|
const params = getUrlParams(window.location.search, '?');
|
||||||
|
if (!params) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
params.request_type = "code";
|
||||||
|
|
||||||
|
console.log("Sent request_type code to server", params);
|
||||||
|
fetch('?' + new URLSearchParams(params).toString(), {
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
handleAuthenticationCode();
|
||||||
|
|
@ -1,45 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title></title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
|
|
||||||
const serialize = (obj) => {
|
|
||||||
var str = [];
|
|
||||||
for (let p in obj)
|
|
||||||
if (obj.hasOwnProperty(p)) {
|
|
||||||
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
|
|
||||||
}
|
|
||||||
return str.join("&");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded",
|
|
||||||
() => {
|
|
||||||
const hash = window.location.hash.substr(1);
|
|
||||||
let result;
|
|
||||||
|
|
||||||
if (hash === "") {
|
|
||||||
const params = (new URL(document.location)).searchParams;
|
|
||||||
result = {
|
|
||||||
error: params.get("error"),
|
|
||||||
state: params.get("state")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result = hash.split('&').reduce(function(res, item) {
|
|
||||||
const parts = item.split('=');
|
|
||||||
res[parts[0]] = parts[1];
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
{});
|
|
||||||
}
|
|
||||||
window.location = `/auth?${serialize(result)}`;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,7 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netstandard2.1</TargetFrameworks>
|
<TargetFrameworks>netstandard2.1</TargetFrameworks>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
|
||||||
<PackageId>SpotifyAPI.Web.Auth</PackageId>
|
<PackageId>SpotifyAPI.Web.Auth</PackageId>
|
||||||
<Title>SpotifyAPI.Web.Auth</Title>
|
<Title>SpotifyAPI.Web.Auth</Title>
|
||||||
<Authors>Jonas Dellinger</Authors>
|
<Authors>Jonas Dellinger</Authors>
|
||||||
@ -19,7 +18,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="EmbedIO" Version="2.9.2">
|
<PackageReference Include="EmbedIO" Version="3.4.3">
|
||||||
<PrivateAssets>None</PrivateAssets>
|
<PrivateAssets>None</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<ProjectReference Include="..\SpotifyAPI.Web\SpotifyAPI.Web.csproj">
|
<ProjectReference Include="..\SpotifyAPI.Web\SpotifyAPI.Web.csproj">
|
||||||
@ -28,6 +27,10 @@
|
|||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="..\SpotifyAPI.Web\Util\Ensure.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="Resources\**\*" />
|
<EmbeddedResource Include="Resources\**\*" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
1
SpotifyAPI.Web.Examples/CLI/.gitignore
vendored
Normal file
1
SpotifyAPI.Web.Examples/CLI/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
credentials.json
|
17
SpotifyAPI.Web.Examples/CLI/CLI.csproj
Normal file
17
SpotifyAPI.Web.Examples/CLI/CLI.csproj
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\SpotifyAPI.Web\SpotifyAPI.Web.csproj" />
|
||||||
|
<ProjectReference Include="..\..\SpotifyAPI.Web.Auth\SpotifyAPI.Web.Auth.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
94
SpotifyAPI.Web.Examples/CLI/Program.cs
Normal file
94
SpotifyAPI.Web.Examples/CLI/Program.cs
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System;
|
||||||
|
using SpotifyAPI.Web.Auth;
|
||||||
|
using SpotifyAPI.Web.Http;
|
||||||
|
using SpotifyAPI.Web;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace CLI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This is a basic example how to get user access using the Auth package and a CLI Program
|
||||||
|
/// Your spotify app needs to have http://localhost:5000 as redirect uri whitelisted
|
||||||
|
/// </summary>
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
private const string CredentialsPath = "credentials.json";
|
||||||
|
private static readonly string clientId = Environment.GetEnvironmentVariable("SPOTIFY_CLIENT_ID");
|
||||||
|
private static readonly string clientSecret = Environment.GetEnvironmentVariable("SPOTIFY_CLIENT_SECRET");
|
||||||
|
private static EmbedIOAuthServer _server;
|
||||||
|
|
||||||
|
public static async Task<int> Main()
|
||||||
|
{
|
||||||
|
if (File.Exists(CredentialsPath))
|
||||||
|
{
|
||||||
|
await Start();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await StartAuthentication();
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.ReadKey();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task Start()
|
||||||
|
{
|
||||||
|
var json = await File.ReadAllTextAsync(CredentialsPath);
|
||||||
|
var token = JsonConvert.DeserializeObject<AuthorizationCodeTokenResponse>(json);
|
||||||
|
|
||||||
|
var authenticator = new AuthorizationCodeAuthenticator(clientId, clientSecret, token);
|
||||||
|
authenticator.TokenRefreshed += (sender, token) => File.WriteAllText(CredentialsPath, JsonConvert.SerializeObject(token));
|
||||||
|
|
||||||
|
var config = SpotifyClientConfig.CreateDefault()
|
||||||
|
.WithAuthenticator(authenticator);
|
||||||
|
|
||||||
|
var spotify = new SpotifyClient(config);
|
||||||
|
|
||||||
|
var me = await spotify.UserProfile.Current();
|
||||||
|
Console.WriteLine($"Welcome {me.DisplayName} ({me.Id}), your authenticated!");
|
||||||
|
|
||||||
|
var playlists = await spotify.Paginate(await spotify.Playlists.CurrentUsers());
|
||||||
|
Console.WriteLine($"Total Playlists in your Account: {playlists.Count}");
|
||||||
|
|
||||||
|
Environment.Exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task StartAuthentication()
|
||||||
|
{
|
||||||
|
_server = new EmbedIOAuthServer(new Uri("http://localhost:5000"), 5000);
|
||||||
|
await _server.Start();
|
||||||
|
_server.AuthorizationCodeReceived += OnAuthorizationCodeReceived;
|
||||||
|
|
||||||
|
var request = new LoginRequest(clientId, LoginRequest.ResponseType.Code)
|
||||||
|
{
|
||||||
|
Scope = new List<string> { "user-read-email", "user-read-private" }
|
||||||
|
};
|
||||||
|
|
||||||
|
Uri url = _server.BuildLoginUri(request);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
BrowserUtil.Open(url);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Unable to open URL, manually open: {0}", url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task OnAuthorizationCodeReceived(object sender, AuthorizationCodeResponse response)
|
||||||
|
{
|
||||||
|
await _server.Stop();
|
||||||
|
AuthorizationCodeTokenResponse token = await new OAuthClient().RequestToken(
|
||||||
|
new AuthorizationCodeTokenRequest(clientId, clientSecret, response.Code, _server.RedirectUri)
|
||||||
|
);
|
||||||
|
|
||||||
|
await File.WriteAllTextAsync(CredentialsPath, JsonConvert.SerializeObject(token));
|
||||||
|
await Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\SpotifyAPI.Web\SpotifyAPI.Web.csproj" />
|
<ProjectReference Include="..\SpotifyAPI.Web\SpotifyAPI.Web.csproj" />
|
||||||
|
<ProjectReference Include="..\SpotifyAPI.Web.Auth\SpotifyAPI.Web.Auth.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -4,6 +4,6 @@ namespace SpotifyAPI.Web
|
|||||||
{
|
{
|
||||||
public interface IOAuthClient
|
public interface IOAuthClient
|
||||||
{
|
{
|
||||||
Task<TokenResponse> RequestToken(ClientCredentialsRequest request);
|
Task<CredentialsTokenResponse> RequestToken(ClientCredentialsRequest request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,12 +15,23 @@ namespace SpotifyAPI.Web
|
|||||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062")]
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062")]
|
||||||
public OAuthClient(SpotifyClientConfig config) : base(ValidateConfig(config)) { }
|
public OAuthClient(SpotifyClientConfig config) : base(ValidateConfig(config)) { }
|
||||||
|
|
||||||
public Task<TokenResponse> RequestToken(ClientCredentialsRequest request)
|
public Task<CredentialsTokenResponse> RequestToken(ClientCredentialsRequest request)
|
||||||
{
|
{
|
||||||
return RequestToken(request, API);
|
return RequestToken(request, API);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task<TokenResponse> RequestToken(
|
public Task<AuthorizationCodeRefreshResponse> RequestToken(AuthorizationCodeRefreshRequest request)
|
||||||
|
{
|
||||||
|
return RequestToken(request, API);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<AuthorizationCodeTokenResponse> RequestToken(AuthorizationCodeTokenRequest request)
|
||||||
|
{
|
||||||
|
return RequestToken(request, API);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Task<CredentialsTokenResponse> RequestToken(
|
||||||
ClientCredentialsRequest request, IAPIConnector apiConnector
|
ClientCredentialsRequest request, IAPIConnector apiConnector
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
@ -32,13 +43,59 @@ namespace SpotifyAPI.Web
|
|||||||
new KeyValuePair<string, string>("grant_type", "client_credentials")
|
new KeyValuePair<string, string>("grant_type", "client_credentials")
|
||||||
};
|
};
|
||||||
|
|
||||||
var base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{request.ClientId}:{request.ClientSecret}"));
|
return SendOAuthRequest<CredentialsTokenResponse>(apiConnector, form, request.ClientId, request.ClientSecret);
|
||||||
var headers = new Dictionary<string, string>
|
}
|
||||||
|
|
||||||
|
public static Task<AuthorizationCodeRefreshResponse> RequestToken(
|
||||||
|
AuthorizationCodeRefreshRequest request, IAPIConnector apiConnector
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Ensure.ArgumentNotNull(request, nameof(request));
|
||||||
|
Ensure.ArgumentNotNull(apiConnector, nameof(apiConnector));
|
||||||
|
|
||||||
|
var form = new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>("grant_type", "refresh_token"),
|
||||||
|
new KeyValuePair<string, string>("refresh_token", request.RefreshToken)
|
||||||
|
};
|
||||||
|
|
||||||
|
return SendOAuthRequest<AuthorizationCodeRefreshResponse>(apiConnector, form, request.ClientId, request.ClientSecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task<AuthorizationCodeTokenResponse> RequestToken(
|
||||||
|
AuthorizationCodeTokenRequest request, IAPIConnector apiConnector
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Ensure.ArgumentNotNull(request, nameof(request));
|
||||||
|
Ensure.ArgumentNotNull(apiConnector, nameof(apiConnector));
|
||||||
|
|
||||||
|
var form = new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>("grant_type", "authorization_code"),
|
||||||
|
new KeyValuePair<string, string>("code", request.Code),
|
||||||
|
new KeyValuePair<string, string>("redirect_uri", request.RedirectUri.ToString())
|
||||||
|
};
|
||||||
|
|
||||||
|
return SendOAuthRequest<AuthorizationCodeTokenResponse>(apiConnector, form, request.ClientId, request.ClientSecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Task<T> SendOAuthRequest<T>(
|
||||||
|
IAPIConnector apiConnector,
|
||||||
|
List<KeyValuePair<string, string>> form,
|
||||||
|
string clientId,
|
||||||
|
string clientSecret)
|
||||||
|
{
|
||||||
|
var headers = BuildAuthHeader(clientId, clientSecret);
|
||||||
|
return apiConnector.Post<T>(SpotifyUrls.OAuthToken, null, new FormUrlEncodedContent(form), headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, string> BuildAuthHeader(string clientId, string clientSecret)
|
||||||
|
{
|
||||||
|
var base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{clientId}:{clientSecret}"));
|
||||||
|
return new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
{ "Authorization", $"Basic {base64}"}
|
{ "Authorization", $"Basic {base64}"}
|
||||||
};
|
};
|
||||||
|
|
||||||
return apiConnector.Post<TokenResponse>(SpotifyUrls.OAuthToken, null, new FormUrlEncodedContent(form), headers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static APIConnector ValidateConfig(SpotifyClientConfig config)
|
private static APIConnector ValidateConfig(SpotifyClientConfig config)
|
||||||
|
@ -212,7 +212,9 @@ namespace SpotifyAPI.Web.Http
|
|||||||
|
|
||||||
private async Task ApplyAuthenticator(IRequest request)
|
private async Task ApplyAuthenticator(IRequest request)
|
||||||
{
|
{
|
||||||
if (_authenticator != null && !request.Endpoint.IsAbsoluteUri)
|
if (_authenticator != null
|
||||||
|
&& !request.Endpoint.IsAbsoluteUri
|
||||||
|
|| request.Endpoint.AbsoluteUri.Contains("https://api.spotify.com", StringComparison.InvariantCulture))
|
||||||
{
|
{
|
||||||
await _authenticator.Apply(request, this).ConfigureAwait(false);
|
await _authenticator.Apply(request, this).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SpotifyAPI.Web.Http
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This Authenticator requests new credentials token on demand and stores them into memory.
|
||||||
|
/// It is unable to query user specifc details.
|
||||||
|
/// </summary>
|
||||||
|
public class AuthorizationCodeAuthenticator : IAuthenticator
|
||||||
|
{
|
||||||
|
public event EventHandler<AuthorizationCodeTokenResponse> TokenRefreshed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initiate a new instance. The token will be refreshed once it expires.
|
||||||
|
/// The initialToken will be updated with the new values on refresh!
|
||||||
|
/// </summary>
|
||||||
|
public AuthorizationCodeAuthenticator(string clientId, string clientSecret, AuthorizationCodeTokenResponse initialToken)
|
||||||
|
{
|
||||||
|
Ensure.ArgumentNotNull(clientId, nameof(clientId));
|
||||||
|
Ensure.ArgumentNotNull(clientSecret, nameof(clientSecret));
|
||||||
|
Ensure.ArgumentNotNull(initialToken, nameof(initialToken));
|
||||||
|
|
||||||
|
InitialToken = initialToken;
|
||||||
|
ClientId = clientId;
|
||||||
|
ClientSecret = clientSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ClientID, defined in a spotify application in your Spotify Developer Dashboard
|
||||||
|
/// </summary>
|
||||||
|
public string ClientId { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ClientID, defined in a spotify application in your Spotify Developer Dashboard
|
||||||
|
/// </summary>
|
||||||
|
public string ClientSecret { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The inital token passed to the authenticator. Fields will be updated on refresh.
|
||||||
|
/// </summary>
|
||||||
|
/// <value></value>
|
||||||
|
public AuthorizationCodeTokenResponse InitialToken { get; }
|
||||||
|
|
||||||
|
public async Task Apply(IRequest request, IAPIConnector apiConnector)
|
||||||
|
{
|
||||||
|
Ensure.ArgumentNotNull(request, nameof(request));
|
||||||
|
|
||||||
|
if (InitialToken.IsExpired)
|
||||||
|
{
|
||||||
|
var tokenRequest = new AuthorizationCodeRefreshRequest(ClientId, ClientSecret, InitialToken.RefreshToken);
|
||||||
|
var refreshedToken = await OAuthClient.RequestToken(tokenRequest, apiConnector).ConfigureAwait(false);
|
||||||
|
|
||||||
|
InitialToken.AccessToken = refreshedToken.AccessToken;
|
||||||
|
InitialToken.CreatedAt = refreshedToken.CreatedAt;
|
||||||
|
InitialToken.ExpiresIn = refreshedToken.ExpiresIn;
|
||||||
|
InitialToken.Scope = refreshedToken.Scope;
|
||||||
|
InitialToken.TokenType = refreshedToken.TokenType;
|
||||||
|
|
||||||
|
TokenRefreshed?.Invoke(this, InitialToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
request.Headers["Authorization"] = $"{InitialToken.TokenType} {InitialToken.AccessToken}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,7 @@ namespace SpotifyAPI.Web.Http
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class CredentialsAuthenticator : IAuthenticator
|
public class CredentialsAuthenticator : IAuthenticator
|
||||||
{
|
{
|
||||||
private TokenResponse _token;
|
private CredentialsTokenResponse _token;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initiate a new instance. The first token will be fetched when the first API call occurs
|
/// Initiate a new instance. The first token will be fetched when the first API call occurs
|
||||||
@ -36,7 +36,7 @@ namespace SpotifyAPI.Web.Http
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The ClientID, defined in a spotify application in your Spotify Developer Dashboard
|
/// The ClientID, defined in a spotify application in your Spotify Developer Dashboard
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientSecret { get; set; }
|
public string ClientSecret { get; }
|
||||||
|
|
||||||
public async Task Apply(IRequest request, IAPIConnector apiConnector)
|
public async Task Apply(IRequest request, IAPIConnector apiConnector)
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
namespace SpotifyAPI.Web
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used when requesting a refreshed token from spotify oauth services (Authorization Code Auth)
|
||||||
|
/// </summary>
|
||||||
|
public class AuthorizationCodeRefreshRequest
|
||||||
|
{
|
||||||
|
public AuthorizationCodeRefreshRequest(string clientId, string clientSecret, string refreshToken)
|
||||||
|
{
|
||||||
|
Ensure.ArgumentNotNullOrEmptyString(clientId, nameof(clientId));
|
||||||
|
Ensure.ArgumentNotNullOrEmptyString(clientSecret, nameof(clientSecret));
|
||||||
|
Ensure.ArgumentNotNullOrEmptyString(refreshToken, nameof(refreshToken));
|
||||||
|
|
||||||
|
ClientId = clientId;
|
||||||
|
ClientSecret = clientSecret;
|
||||||
|
RefreshToken = refreshToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string RefreshToken { get; }
|
||||||
|
public string ClientId { get; }
|
||||||
|
public string ClientSecret { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
namespace SpotifyAPI.Web
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used when requesting a token from spotify oauth services (Authorization Code Auth)
|
||||||
|
/// </summary>
|
||||||
|
public class AuthorizationCodeTokenRequest
|
||||||
|
{
|
||||||
|
public AuthorizationCodeTokenRequest(string clientId, string clientSecret, string code, Uri redirectUri)
|
||||||
|
{
|
||||||
|
Ensure.ArgumentNotNullOrEmptyString(clientId, nameof(clientId));
|
||||||
|
Ensure.ArgumentNotNullOrEmptyString(clientSecret, nameof(clientSecret));
|
||||||
|
Ensure.ArgumentNotNullOrEmptyString(code, nameof(code));
|
||||||
|
Ensure.ArgumentNotNull(redirectUri, nameof(redirectUri));
|
||||||
|
|
||||||
|
ClientId = clientId;
|
||||||
|
ClientSecret = clientSecret;
|
||||||
|
Code = code;
|
||||||
|
RedirectUri = redirectUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ClientId { get; }
|
||||||
|
public string ClientSecret { get; }
|
||||||
|
public string Code { get; }
|
||||||
|
public Uri RedirectUri { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,8 @@
|
|||||||
namespace SpotifyAPI.Web
|
namespace SpotifyAPI.Web
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used when requesting a token from spotify oauth services (Client Credentials Auth)
|
||||||
|
/// </summary>
|
||||||
public class ClientCredentialsRequest
|
public class ClientCredentialsRequest
|
||||||
{
|
{
|
||||||
public ClientCredentialsRequest(string clientId, string clientSecret)
|
public ClientCredentialsRequest(string clientId, string clientSecret)
|
@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace SpotifyAPI.Web
|
||||||
|
{
|
||||||
|
public class AuthorizationCodeRefreshResponse
|
||||||
|
{
|
||||||
|
public string AccessToken { get; set; }
|
||||||
|
public string TokenType { get; set; }
|
||||||
|
public int ExpiresIn { get; set; }
|
||||||
|
public string Scope { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Auto-Initalized to UTC Now
|
||||||
|
/// </summary>
|
||||||
|
/// <value></value>
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
public bool IsExpired { get => CreatedAt.AddSeconds(ExpiresIn) <= DateTime.UtcNow; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace SpotifyAPI.Web
|
||||||
|
{
|
||||||
|
public class AuthorizationCodeTokenResponse
|
||||||
|
{
|
||||||
|
public string AccessToken { get; set; }
|
||||||
|
public string TokenType { get; set; }
|
||||||
|
public int ExpiresIn { get; set; }
|
||||||
|
public string Scope { get; set; }
|
||||||
|
public string RefreshToken { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Auto-Initalized to UTC Now
|
||||||
|
/// </summary>
|
||||||
|
/// <value></value>
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
public bool IsExpired { get => CreatedAt.AddSeconds(ExpiresIn) <= DateTime.UtcNow; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
namespace SpotifyAPI.Web
|
namespace SpotifyAPI.Web
|
||||||
{
|
{
|
||||||
public class TokenResponse
|
public class CredentialsTokenResponse
|
||||||
{
|
{
|
||||||
public string AccessToken { get; set; }
|
public string AccessToken { get; set; }
|
||||||
public string TokenType { get; set; }
|
public string TokenType { get; set; }
|
25
SpotifyAPI.Web/Models/Scopes.cs
Normal file
25
SpotifyAPI.Web/Models/Scopes.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
namespace SpotifyAPI.Web
|
||||||
|
{
|
||||||
|
public static class Scopes
|
||||||
|
{
|
||||||
|
public const string UgcImageUpload = "ugc-image-upload";
|
||||||
|
public const string UserReadPlaybackState = "user-read-playback-state";
|
||||||
|
public const string UserModifyPlaybackState = "user-modify-playback-state";
|
||||||
|
public const string UserReadCurrentlyPlaying = "user-read-currently-playing";
|
||||||
|
public const string Streaming = "streaming";
|
||||||
|
public const string AppRemoteControl = "app-remote-control";
|
||||||
|
public const string UserReadEmail = "user-read-email";
|
||||||
|
public const string UserReadPrivate = "user-read-private";
|
||||||
|
public const string PlaylistReadCollaborative = "playlist-read-collaborative";
|
||||||
|
public const string PlaylistModifyPublic = "playlist-modify-public";
|
||||||
|
public const string PlaylistReadPrivate = "playlist-read-private";
|
||||||
|
public const string PlaylistModifyPrivate = "playlist-modify-private";
|
||||||
|
public const string UserLibraryModify = "user-library-modify";
|
||||||
|
public const string UserLibraryRead = "user-library-read";
|
||||||
|
public const string UserTopRead = "user-top-read";
|
||||||
|
public const string UserReadPlaybackPosition = "user-read-playback-position";
|
||||||
|
public const string UserReadRecentlyPlayed = "user-read-recently-played";
|
||||||
|
public const string UserFollowRead = "user-follow-read";
|
||||||
|
public const string UserFollowModify = "user-follow-modify";
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,8 @@ namespace SpotifyAPI.Web
|
|||||||
|
|
||||||
public static readonly Uri APIV1 = new Uri("https://api.spotify.com/v1/");
|
public static readonly Uri APIV1 = new Uri("https://api.spotify.com/v1/");
|
||||||
|
|
||||||
|
public static readonly Uri Authorize = new Uri("https://accounts.spotify.com/authorize");
|
||||||
|
|
||||||
public static readonly Uri OAuthToken = new Uri("https://accounts.spotify.com/api/token");
|
public static readonly Uri OAuthToken = new Uri("https://accounts.spotify.com/api/token");
|
||||||
|
|
||||||
public static Uri Me() => EUri($"me");
|
public static Uri Me() => EUri($"me");
|
||||||
|
@ -9,6 +9,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpotifyAPI.Web.Tests", "Spo
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpotifyAPI.Web.Auth", "SpotifyAPI.Web.Auth\SpotifyAPI.Web.Auth.csproj", "{400A3787-FDBE-4A4C-9DDD-AAEB3DCE1DF5}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpotifyAPI.Web.Auth", "SpotifyAPI.Web.Auth\SpotifyAPI.Web.Auth.csproj", "{400A3787-FDBE-4A4C-9DDD-AAEB3DCE1DF5}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SpotifyAPI.Web.Examples", "SpotifyAPI.Web.Examples", "{48A7DE65-29BB-409C-AC45-77F6586C0B15}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CLI", "SpotifyAPI.Web.Examples\CLI\CLI.csproj", "{F4ECE937-99F2-4C4F-9F5C-4AB875D9538A}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -27,6 +31,10 @@ Global
|
|||||||
{400A3787-FDBE-4A4C-9DDD-AAEB3DCE1DF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{400A3787-FDBE-4A4C-9DDD-AAEB3DCE1DF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{400A3787-FDBE-4A4C-9DDD-AAEB3DCE1DF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{400A3787-FDBE-4A4C-9DDD-AAEB3DCE1DF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{400A3787-FDBE-4A4C-9DDD-AAEB3DCE1DF5}.Release|Any CPU.Build.0 = Release|Any CPU
|
{400A3787-FDBE-4A4C-9DDD-AAEB3DCE1DF5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{F4ECE937-99F2-4C4F-9F5C-4AB875D9538A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{F4ECE937-99F2-4C4F-9F5C-4AB875D9538A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{F4ECE937-99F2-4C4F-9F5C-4AB875D9538A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{F4ECE937-99F2-4C4F-9F5C-4AB875D9538A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -35,5 +43,6 @@ Global
|
|||||||
SolutionGuid = {097062B8-0E87-43C8-BD98-61843A68BE6D}
|
SolutionGuid = {097062B8-0E87-43C8-BD98-61843A68BE6D}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{F4ECE937-99F2-4C4F-9F5C-4AB875D9538A} = {48A7DE65-29BB-409C-AC45-77F6586C0B15}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
Loading…
Reference in New Issue
Block a user