| @@ -1,131 +0,0 @@ | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Text.Json.Serialization; | |||
| using System.Threading.Tasks; | |||
| namespace Grpc.Shared.DTOs.Search | |||
| { | |||
| [ProtoContract] | |||
| internal class SearchDTO | |||
| { | |||
| public partial class SearchContracts | |||
| { | |||
| [ProtoMember(1)] | |||
| public Tracks? Tracks { get; set; } | |||
| } | |||
| public partial class Tracks | |||
| { | |||
| [ProtoMember(1)] | |||
| public Uri Href { get; set; } | |||
| [ProtoMember(2)] | |||
| public Items[]? Items { get; set; } | |||
| } | |||
| public partial class Items | |||
| { | |||
| [ProtoMember(1)] | |||
| public Album Album { get; set; } | |||
| [ProtoMember(2)] | |||
| public Artist[] Artists { get; set; } | |||
| [ProtoMember(3)] | |||
| public long DurationMs { get; set; } | |||
| [ProtoMember(4)] | |||
| public ExternalUrls ExternalUrls { get; set; } | |||
| [ProtoMember(5)] | |||
| public Uri Href { get; set; } | |||
| [ProtoMember(6)] | |||
| public string Id { get; set; } | |||
| [ProtoMember(7)] | |||
| public string Name { get; set; } | |||
| [ProtoMember(8)] | |||
| public long Popularity { get; set; } | |||
| [ProtoMember(9)] | |||
| public long TrackNumber { get; set; } | |||
| [ProtoMember(10)] | |||
| public string Type { get; set; } | |||
| [ProtoMember(11)] | |||
| public string Uri { get; set; } | |||
| } | |||
| public partial class Album | |||
| { | |||
| [ProtoMember(1)] | |||
| public Uri Href { get; set; } | |||
| [ProtoMember(2)] | |||
| public string Id { get; set; } | |||
| [ProtoMember(3)] | |||
| public Image[] Images { get; set; } | |||
| [ProtoMember(4)] | |||
| public string Name { get; set; } | |||
| [ProtoMember(5)] | |||
| public DateTimeOffset ReleaseDate { get; set; } | |||
| [ProtoMember(6)] | |||
| public long TotalTracks { get; set; } | |||
| [ProtoMember(7)] | |||
| public string Type { get; set; } | |||
| [ProtoMember(8)] | |||
| public string Uri { get; set; } | |||
| } | |||
| public partial class Image | |||
| { | |||
| [ProtoMember(1)] | |||
| public long Height { get; set; } | |||
| [ProtoMember(2)] | |||
| public Uri Url { get; set; } | |||
| [ProtoMember(3)] | |||
| public long Width { get; set; } | |||
| } | |||
| public partial class Artist | |||
| { | |||
| [ProtoMember(1)] | |||
| public ExternalUrls ExternalUrls { get; set; } | |||
| [ProtoMember(2)] | |||
| public Uri Href { get; set; } | |||
| [ProtoMember(3)] | |||
| public string Id { get; set; } | |||
| [ProtoMember(4)] | |||
| public string Name { get; set; } | |||
| [ProtoMember(5)] | |||
| public string Type { get; set; } | |||
| [ProtoMember(6)] | |||
| public string Uri { get; set; } | |||
| } | |||
| public partial class ExternalUrls | |||
| { | |||
| [ProtoMember(1)] | |||
| public Uri Spotify { get; set; } | |||
| } | |||
| } | |||
| } | |||
| @@ -1,14 +0,0 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Grpc.Shared.DTOs.Search | |||
| { | |||
| internal class SearchRequestDTO | |||
| { | |||
| } | |||
| } | |||
| @@ -1,14 +0,0 @@ | |||
| <Project Sdk="Microsoft.NET.Sdk"> | |||
| <PropertyGroup> | |||
| <TargetFramework>net6.0</TargetFramework> | |||
| <ImplicitUsings>enable</ImplicitUsings> | |||
| <Nullable>enable</Nullable> | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="protobuf-net.Core" Version="3.1.17" /> | |||
| <PackageReference Include="System.ServiceModel.Primitives" Version="4.5.3" /> | |||
| </ItemGroup> | |||
| </Project> | |||
| @@ -1,34 +0,0 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Runtime.Serialization; | |||
| using System.ServiceModel; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.Interfaces | |||
| { | |||
| [ServiceContract] | |||
| public interface ISearchService | |||
| { | |||
| Task<SearchResult> SearchTracks(SearchRequest req); | |||
| } | |||
| [DataContract] | |||
| public class SearchResult | |||
| { | |||
| [DataMember(Order = 1)] | |||
| public string NewQuery { get; set; } | |||
| [DataMember(Order = 2)] | |||
| public string NewType { get; set; } | |||
| } | |||
| [DataContract] | |||
| public class SearchRequest | |||
| { | |||
| [DataMember(Order = 1)] | |||
| public string Query { get; set; } | |||
| [DataMember(Order = 2)] | |||
| public string Type { get; set; } = "track"; | |||
| } | |||
| } | |||
| @@ -1,16 +0,0 @@ | |||
| using ProtoBuf; | |||
| namespace GrpcShared | |||
| { | |||
| [ProtoContract] | |||
| public class AuthParams | |||
| { | |||
| [ProtoMember(1)] | |||
| public string ClientId { get; set; } | |||
| [ProtoMember(2)] | |||
| public string RedirectURI { get; set; } | |||
| [ProtoMember(3)] | |||
| public string Scope { get; set; } | |||
| } | |||
| } | |||
| @@ -1,26 +0,0 @@ | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.Auth | |||
| { | |||
| [ProtoContract] | |||
| public class AuthRequest | |||
| { | |||
| [ProtoMember(1)] | |||
| public string ClientId { get; set; } | |||
| [ProtoMember(2)] | |||
| public string ClientSecret { get; set; } | |||
| [ProtoMember(3)] | |||
| public string RedirectURI { get; set; } | |||
| [ProtoMember(4)] | |||
| public string ResponseType { get; set; } = "track"; | |||
| [ProtoMember(5)] | |||
| public string Scope { get; set; } | |||
| [ProtoMember(6)] | |||
| public string ShowDialog{ get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,28 @@ | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.ComponentModel; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.Auth | |||
| { | |||
| [ProtoContract] | |||
| public class CodeRequest | |||
| { | |||
| [ProtoMember(1)] | |||
| public string? ClientId { get; set; } | |||
| [ProtoMember(2)] | |||
| public string? ClientSecret { get; set; } | |||
| [ProtoMember(3)] | |||
| public string? RedirectURI { get; set; } | |||
| [ProtoMember(4)] | |||
| public string ResponseType { get; set; } = "code"; | |||
| [ProtoMember(5)] | |||
| public string? Scope { get; set; } | |||
| [ProtoMember(6)] | |||
| [DefaultValue(true)] | |||
| public bool ShowDialog { get; set; } = true; | |||
| } | |||
| } | |||
| @@ -1,16 +0,0 @@ | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.Auth | |||
| { | |||
| [ProtoContract] | |||
| public class CodeResponse | |||
| { | |||
| [ProtoMember(1)] | |||
| public string Code { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,22 @@ | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.Auth | |||
| { | |||
| [ProtoContract] | |||
| public class TokenRequest | |||
| { | |||
| [ProtoMember(1)] | |||
| public string grant_type { get; set; } = "authorization_code"; | |||
| [ProtoMember(2)] | |||
| public string? code { get; set; } | |||
| [ProtoMember(3)] | |||
| public string? redirect_uri { get; set; } | |||
| } | |||
| } | |||
| @@ -8,11 +8,13 @@ using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.Auth | |||
| { | |||
| [ProtoContract] | |||
| public class AuthResponse | |||
| public class TokenResponse | |||
| { | |||
| [ProtoMember(1)] | |||
| public string AccessToken { get; set; } | |||
| public string? access_token { get; set; } | |||
| [ProtoMember(2)] | |||
| public string RefreshToken{ get; set; } | |||
| public string? refresh_token{ get; set; } | |||
| [ProtoMember(3)] | |||
| public int? expires_in { get; set; } | |||
| } | |||
| } | |||
| @@ -6,8 +6,8 @@ namespace GrpcShared.Interfaces | |||
| [Service] | |||
| public interface IAuthService | |||
| { | |||
| Task<CodeResponse> GetCode(AuthRequest request); | |||
| Task<AuthResponse> GetAccessToken(CodeResponse code); | |||
| Task<AuthParams> GetAuthParams(); | |||
| Task<TokenResponse> GetAccessToken(TokenRequest code); | |||
| Task<CodeRequest> GetAuthParams(); | |||
| //Task<ClientSecrets> GetClientSecrets(); | |||
| } | |||
| } | |||
| @@ -10,6 +10,7 @@ | |||
| <PackageReference Include="Grpc.AspNetCore" Version="2.40.0" /> | |||
| <PackageReference Include="Grpc.AspNetCore.Web" Version="2.47.0" /> | |||
| <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="6.0.8" /> | |||
| <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> | |||
| <PackageReference Include="protobuf-net.Grpc" Version="1.0.171" /> | |||
| <PackageReference Include="protobuf-net.Grpc.AspNetCore" Version="1.0.152" /> | |||
| <PackageReference Include="protobuf-net.Grpc.AspNetCore.Reflection" Version="1.0.152" /> | |||
| @@ -3,6 +3,7 @@ using IdentityProvider.Services; | |||
| using Microsoft.AspNetCore.Server.Kestrel.Core; | |||
| using ProtoBuf.Grpc.Server; | |||
| using Microsoft.Extensions.Options; | |||
| using GrpcShared.DTO.Auth; | |||
| var builder = WebApplication.CreateBuilder(args); | |||
| #if DEBUG | |||
| @@ -20,7 +21,7 @@ builder.WebHost.ConfigureKestrel(options => | |||
| builder.Services.AddOptions(); | |||
| // Additional configuration is required to successfully run gRPC on macOS. | |||
| // For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682 | |||
| builder.Services.Configure<AuthParams>(builder.Configuration.GetSection("AuthParams")); | |||
| builder.Services.Configure<CodeRequest>(builder.Configuration.GetSection("AuthParams")); | |||
| builder.Services.AddControllersWithViews(); | |||
| builder.Services.AddRazorPages(); | |||
| @@ -29,6 +30,8 @@ builder.Services.AddEndpointsApiExplorer(); | |||
| builder.Services.AddGrpc(); | |||
| builder.Services.AddCodeFirstGrpc(); | |||
| builder.Services.AddCodeFirstGrpcReflection(); | |||
| //call spotify api | |||
| builder.Services.AddHttpClient(); | |||
| var app = builder.Build(); | |||
| @@ -46,11 +49,13 @@ else | |||
| app.UseHttpsRedirection(); | |||
| //run blazor project by running grpc server | |||
| app.UseBlazorFrameworkFiles(); | |||
| app.UseStaticFiles(); | |||
| app.UseRouting(); | |||
| //for http2 -> http conversion | |||
| app.UseGrpcWeb(); | |||
| app.MapRazorPages(); | |||
| @@ -1,39 +1,78 @@ | |||
| //using IdentityProvider.Protos.AuthService; | |||
| using Grpc.Net.Client; | |||
| using GrpcShared; | |||
| using GrpcShared.DTO.Auth; | |||
| using GrpcShared.Interfaces; | |||
| using Microsoft.Extensions.Options; | |||
| using Microsoft.Net.Http.Headers; | |||
| using Newtonsoft.Json; | |||
| using System.Net.Http.Headers; | |||
| using System.Text; | |||
| using System.Text.Json; | |||
| namespace IdentityProvider.Services | |||
| { | |||
| public class AuthService : IAuthService | |||
| { | |||
| private readonly ILogger<AuthService> _logger; | |||
| private readonly AuthParams _params; | |||
| public AuthService(ILogger<AuthService> logger, IOptions<AuthParams> options ) | |||
| private readonly CodeRequest _params; | |||
| private readonly IHttpClientFactory _httpClientFactory; | |||
| public AuthService(ILogger<AuthService> logger, IOptions<CodeRequest> options, IHttpClientFactory httpClientFactory) | |||
| { | |||
| _logger = logger; | |||
| _params = options.Value; | |||
| _httpClientFactory = httpClientFactory; | |||
| } | |||
| public Task<AuthResponse> GetAccessToken(CodeResponse code) | |||
| public async Task<TokenResponse> GetAccessToken(TokenRequest tokenRequest) | |||
| { | |||
| throw new NotImplementedException(); | |||
| } | |||
| var http = _httpClientFactory.CreateClient(); | |||
| public Task<CodeResponse> GetCode(AuthRequest request) | |||
| { | |||
| throw new NotImplementedException(); | |||
| string url = "https://accounts.spotify.com/api/token"; | |||
| http.BaseAddress = new Uri(url); | |||
| //get client id and secret, and redirect uri from appsettings, convert to base64 and set as header | |||
| var secrets = await GetAuthParams(); | |||
| byte[] contentType = Encoding.UTF8.GetBytes($"{secrets.ClientId}:{secrets.ClientSecret}"); | |||
| tokenRequest.redirect_uri = secrets.RedirectURI; | |||
| //AUTHORIZATION HEADER | |||
| http.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Basic " + Convert.ToBase64String(contentType)); | |||
| //ACCEPT HEADER | |||
| http.DefaultRequestHeaders.Accept.Add( | |||
| new MediaTypeWithQualityHeaderValue("application/json")); | |||
| //BODY PARAMS | |||
| var requestBody = new Dictionary<string, string>(); | |||
| requestBody["grant_type"] = tokenRequest.grant_type; | |||
| requestBody["code"] = tokenRequest.code!; | |||
| requestBody["redirect_uri"] = tokenRequest.redirect_uri!; | |||
| //REQUEST | |||
| var response = await http.PostAsync(url, new FormUrlEncodedContent(requestBody)); | |||
| var contents = JsonConvert.DeserializeObject<TokenResponse>(await response.Content.ReadAsStringAsync()); | |||
| return new TokenResponse | |||
| { | |||
| access_token = contents!.access_token, | |||
| refresh_token = contents!.refresh_token, | |||
| expires_in = contents!.expires_in | |||
| }; | |||
| } | |||
| public async Task<AuthParams> GetAuthParams() | |||
| public async Task<CodeRequest> GetAuthParams() | |||
| { | |||
| var authParams = new AuthParams { | |||
| var authParams = new CodeRequest | |||
| { | |||
| ClientId = _params.ClientId, | |||
| RedirectURI = _params.RedirectURI, | |||
| Scope =_params.Scope }; | |||
| Scope = _params.Scope, | |||
| ClientSecret = _params.ClientSecret | |||
| }; | |||
| return await Task.FromResult(authParams); | |||
| } | |||
| } | |||
| } | |||
| @@ -14,6 +14,7 @@ | |||
| "AuthParams": { | |||
| "ClientId": "83e1d09876b049c4bb1953185a4b3bfb", | |||
| "RedirectURI": "https://localhost:44342/callback", | |||
| "Scope": "user-read-currently-playing user-read-email user-library-modify user-top-read" | |||
| "Scope": "user-read-currently-playing user-read-email user-library-modify user-top-read", | |||
| "ClientSecret": "ea752433d0774fad87fab5c1ee788c8d" | |||
| } | |||
| } | |||
| @@ -7,6 +7,7 @@ | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="Blazored.SessionStorage" Version="2.2.0" /> | |||
| <PackageReference Include="Grpc.Net.Client" Version="2.47.0" /> | |||
| <PackageReference Include="Grpc.Net.Client.Web" Version="2.47.0" /> | |||
| <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.7" /> | |||
| @@ -0,0 +1,37 @@ | |||
| @page "/callback" | |||
| @using NemAnBlazor.Services.Interfaces | |||
| @inject NavigationManager NavigationMgr | |||
| @inject IAuthClientService AuthService | |||
| @inject Blazored.SessionStorage.ISessionStorageService sessionStorage | |||
| <PageTitle>Callback page</PageTitle> | |||
| <p role="status">Current count: @currentCount</p> | |||
| <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> | |||
| @code { | |||
| private int currentCount = 0; | |||
| private void IncrementCount() | |||
| { | |||
| currentCount++; | |||
| } | |||
| protected override async Task OnInitializedAsync() | |||
| { | |||
| string url = NavigationMgr.Uri; | |||
| //code is the only parameter in the url | |||
| string code = url.Split("=")[1]; | |||
| var response = await AuthService.GetAccessToken(new GrpcShared.DTO.Auth.TokenRequest { code = code}); | |||
| //store access token in local storage | |||
| await sessionStorage.SetItemAsync("token", response.access_token); | |||
| await sessionStorage.SetItemAsync("refresh_token", response.refresh_token); | |||
| //redirect to home | |||
| NavigationMgr.NavigateTo("/home"); | |||
| } | |||
| } | |||
| @@ -1,18 +0,0 @@ | |||
| @page "/counter" | |||
| <PageTitle>Counter</PageTitle> | |||
| <h1>Counter</h1> | |||
| <p role="status">Current count: @currentCount</p> | |||
| <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> | |||
| @code { | |||
| private int currentCount = 0; | |||
| private void IncrementCount() | |||
| { | |||
| currentCount++; | |||
| } | |||
| } | |||
| @@ -0,0 +1,7 @@ | |||
| @page "/home" | |||
| <h3>Home</h3> | |||
| <p>pozdrav, login radi</p> | |||
| @code { | |||
| } | |||
| @@ -19,11 +19,9 @@ Dobrodošli u našu NemAn aplikaciju. | |||
| protected override async Task OnInitializedAsync() | |||
| { | |||
| //var response = await SearchService.GetListSearchAsync(new GrpcShared.DTO.Search.SearchRequest() { Query="venom", Type = "track"}); | |||
| AuthParams authParams = await AuthService.GetAuthParams(); | |||
| CodeRequest authParams = await AuthService.GetAuthParams(); | |||
| // await AuthService.GetAccessToken(new CodeResponse{ Code = "hello"}); | |||
| AuthRequest request = new() { ResponseType = "code", Scope = authParams.Scope, ClientId = authParams.ClientId, RedirectURI = authParams.RedirectURI}; | |||
| string url = $"https://accounts.spotify.com/en/authorize?client_id={request.ClientId}&redirect_uri={request.RedirectURI}&response_type={request.ResponseType}&scope={request.Scope}&show_dialog=true"; | |||
| string url = $"https://accounts.spotify.com/en/authorize?client_id={authParams.ClientId}&redirect_uri={authParams.RedirectURI}&response_type={authParams.ResponseType}&scope={authParams.Scope}&show_dialog={authParams.ShowDialog}"; | |||
| NavigationManager.NavigateTo(url); | |||
| } | |||
| @@ -1,3 +1,4 @@ | |||
| using Blazored.SessionStorage; | |||
| using Grpc.Net.Client; | |||
| using Grpc.Net.Client.Web; | |||
| using Microsoft.AspNetCore.Components; | |||
| @@ -23,6 +24,7 @@ builder.Services.AddScoped(_ => | |||
| builder.Services.AddScoped<ISearchClientService, SearchClientService>(); | |||
| builder.Services.AddScoped<IAuthClientService, AuthClientService>(); | |||
| builder.Services.AddBlazoredSessionStorage(); | |||
| await builder.Build().RunAsync(); | |||
| @@ -15,19 +15,15 @@ namespace NemAnBlazor.Services | |||
| { | |||
| _serviceClient = grpcChannel.CreateGrpcService<IAuthService>(); | |||
| } | |||
| public async Task<AuthResponse> GetAccessToken(CodeResponse code) | |||
| public async Task<TokenResponse> GetAccessToken(TokenRequest request) | |||
| { | |||
| return await _serviceClient.GetAccessToken(code); | |||
| return await _serviceClient.GetAccessToken(request); | |||
| } | |||
| public async Task <AuthParams> GetAuthParams() | |||
| public async Task<CodeRequest> GetAuthParams() | |||
| { | |||
| return await _serviceClient.GetAuthParams(); | |||
| } | |||
| public Task<CodeResponse> GetCode(AuthRequest request) | |||
| { | |||
| throw new NotImplementedException(); | |||
| } | |||
| } | |||
| } | |||
| @@ -5,8 +5,7 @@ namespace NemAnBlazor.Services.Interfaces | |||
| { | |||
| public interface IAuthClientService | |||
| { | |||
| Task<CodeResponse> GetCode(AuthRequest request); | |||
| Task<AuthResponse> GetAccessToken(CodeResponse code); | |||
| Task<AuthParams> GetAuthParams(); | |||
| Task<TokenResponse> GetAccessToken(TokenRequest tokenRequest); | |||
| Task<CodeRequest> GetAuthParams(); | |||
| } | |||
| } | |||
| @@ -15,7 +15,7 @@ | |||
| </NavLink> | |||
| </div> | |||
| <div class="nav-item px-3"> | |||
| <NavLink class="nav-link" href="counter"> | |||
| <NavLink class="nav-link" href="callback"> | |||
| <span class="oi oi-plus" aria-hidden="true"></span> Counter | |||
| </NavLink> | |||
| </div> | |||
| @@ -8,3 +8,4 @@ | |||
| @using Microsoft.JSInterop | |||
| @using NemAnBlazor | |||
| @using NemAnBlazor.Shared | |||
| @using System.Web | |||