diff --git a/.github/workflows/aspnetcore.yml b/.github/workflows/aspnetcore.yml new file mode 100644 index 0000000..da8ca1e --- /dev/null +++ b/.github/workflows/aspnetcore.yml @@ -0,0 +1,17 @@ +name: ASP.NET Core CI + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@master + - name: Setup .NET Core + uses: actions/setup-dotnet@v1 + with: + version: 2.2.108 + - name: Build + run: dotnet build ./src/Auth0.sln --configuration Release diff --git a/Auth0.pptx b/Auth0.pptx new file mode 100644 index 0000000..d0fea27 Binary files /dev/null and b/Auth0.pptx differ diff --git a/README.md b/README.md index 8103b39..c06b9a9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,10 @@ -# Auth0-Coding-Project +# Auth0 Coding Project + +

+ GitHub Actions status +

+ +[![Build Status](https://dev.azure.com/olegburov/Auth0/_apis/build/status/olegburov.Auth0?branchName=master)](https://dev.azure.com/olegburov/Auth0/_build/latest?definitionId=10&branchName=master) ## Problem Overview @@ -24,4 +30,101 @@ Build a sample application (SPA or Web Application) and a dummy service that dep - Enable usage of `refresh_token` for mobile applications. - Add your own custom rule (not from a template) that enriches the user profile. -Please share the code on GitHub and make sure the README file is clean and clear so customers can understand it. \ No newline at end of file +Please share the code on GitHub and make sure the README file is clean and clear so customers can understand it. + +## Application flow diagram + +![](Schema.png) + +## Getting started + +1. Navigate at URL https://onegit-webapp.azurewebsites.net/. + +2. Log in using one of the following predefined users: + + - User with the access right `Read Data`. + + * Login: `viewer@olegburov.com` + * Password: `Viewer2User` + + - User with the access right `Write Data`. + + * Login: `editor@olegburov.com` + * Password: `Editor2User` + + - User with the access right `Delete Data`. + + * Login: `admin@olegburov.com` + * Password: `Admin2User` + +3. Depending on a user's access right, a security role is assigned with the following scope(s) for calling back-end Web API. + + - The access right `Read Data` + + * Role: `reader` + * Scope: `read:repositories` + + - The access right `Write Data` + + * Role: `editor` + * Scope: `create:repositories`, `read:repositories` and `update:repositories` + + - The access right `Delete Data` + + * Role: `admin` + * Scope: `create:repositories`, `read:repositories`, `update:repositories` and `delete:repositories` + +4. Based on a user's role, the front-end WebApp provides differente actions for a user: + + - The role `reader` + + * View existent repositories + + - The role `editor` + + * View existent repositories + * Create new repositories + * Edit existent repositories + + - The role `admin` + + * View existent repositories + * Create new repositories + * Edit existent repositories + * Delete existent repositories + +5. When a user perform granted actions, the front-end WebApp communicates with back-end WebAPI (https://onegit-webapi.azurewebsites.net) using an `access_token` to invoke these actions on behalf of a user. + +6. The `access_token` includes a granted scope(s) based on a user's role. + +7. The WebAPI provides the following endpoints where each requres a specific scope to be executed: + + - The **List Repositories** operation returns a list of the repositories currently in a database. + + `GET https://{base-url}/api/repositories` + +> The scope `read:repositories` is required for service requests. + + - The **Get Repository** operation gets a repository from a database. + + `GET https://{base-url}/api/repositories/{repository-guid}` + +> The scope `read:repositories` is required for service requests. + + - The **Create Repository** operation creates a new repository in a database. + + `POST https://{base-url}/api/repositories` + +> The scope `create:repositories` is required for service requests. + + - The **Update Repository** operation updates an existent repository to the new one. + + `PUT https://{base-url}/api/repositories/{repository-guid}` + +> The scope `update:repositories` is required for service requests. + + - The **Delete Repository** operation removes an repository from database. + + `DELETE https://{base-url}/api/repositories/{repository-guid}` + +> The scope `delete:repositories` is required for service requests. diff --git a/Schema.png b/Schema.png new file mode 100644 index 0000000..a243763 Binary files /dev/null and b/Schema.png differ diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000..304d348 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,21 @@ +# ASP.NET Core +# Build and test ASP.NET Core projects targeting .NET Core. +# Add steps that run tests, create a NuGet package, deploy, and more: +# https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core + +trigger: +- master + +pool: + vmImage: 'ubuntu-latest' + +variables: + buildConfiguration: 'Release' + +steps: +- task: DotNetCoreCLI@2 + displayName: Build + inputs: + command: build + projects: '**/*.csproj' + arguments: '--configuration $(buildConfiguration)' diff --git a/src/.dockerignore b/src/.dockerignore new file mode 100644 index 0000000..df2e0fe --- /dev/null +++ b/src/.dockerignore @@ -0,0 +1,9 @@ +.dockerignore +.env +.git +.gitignore +.vs +.vscode +*/bin +*/obj +**/.toolstarget \ No newline at end of file diff --git a/src/Auth0.sln b/src/Auth0.sln index 1d2ba6d..f8113ec 100644 --- a/src/Auth0.sln +++ b/src/Auth0.sln @@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27428.2037 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OneGit", "OneGit\OneGit.csproj", "{E70F2910-44D3-4A7A-BAB0-EEB46D063E62}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OneGit.Web", "OneGit.Web\OneGit.Web.csproj", "{E70F2910-44D3-4A7A-BAB0-EEB46D063E62}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OneGit.Api", "OneGit.Api\OneGit.Api.csproj", "{9C7AE68D-03F1-4D84-95D9-35887E2CF700}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +17,10 @@ Global {E70F2910-44D3-4A7A-BAB0-EEB46D063E62}.Debug|Any CPU.Build.0 = Debug|Any CPU {E70F2910-44D3-4A7A-BAB0-EEB46D063E62}.Release|Any CPU.ActiveCfg = Release|Any CPU {E70F2910-44D3-4A7A-BAB0-EEB46D063E62}.Release|Any CPU.Build.0 = Release|Any CPU + {9C7AE68D-03F1-4D84-95D9-35887E2CF700}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9C7AE68D-03F1-4D84-95D9-35887E2CF700}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9C7AE68D-03F1-4D84-95D9-35887E2CF700}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9C7AE68D-03F1-4D84-95D9-35887E2CF700}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/OneGit.Api/Authorization/HasScopeHandler.cs b/src/OneGit.Api/Authorization/HasScopeHandler.cs new file mode 100644 index 0000000..4363491 --- /dev/null +++ b/src/OneGit.Api/Authorization/HasScopeHandler.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Authorization; +using System.Linq; +using System.Threading.Tasks; + +namespace OneGit.Api.Authorization +{ + public class HasScopeHandler : AuthorizationHandler + { + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, HasScopeRequirement requirement) + { + // If user does not have the scope claim, get out of here + if (!context.User.HasClaim(claim => claim.Type.Equals("scope") && claim.Issuer == requirement.Issuer)) + { + return Task.CompletedTask; + } + + // Split the scopes string into an array + var scopes = context.User.FindFirst(claim => claim.Type.Equals("scope") && claim.Issuer == requirement.Issuer).Value.Split(' '); + + // Succeed if the scope array contains the required scope + if (scopes.Any(s => s == requirement.Scope)) + { + context.Succeed(requirement); + } + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/OneGit.Api/Authorization/HasScopeRequirement.cs b/src/OneGit.Api/Authorization/HasScopeRequirement.cs new file mode 100644 index 0000000..dff07b4 --- /dev/null +++ b/src/OneGit.Api/Authorization/HasScopeRequirement.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Authorization; +using System; + +namespace OneGit.Api.Authorization +{ + public class HasScopeRequirement : IAuthorizationRequirement + { + public string Issuer { get; } + public string Scope { get; } + + public HasScopeRequirement(string issuer, string scope) + { + Issuer = issuer ?? throw new ArgumentNullException(nameof(issuer), $"The parameter '{nameof(issuer)}' cannot be null."); + Scope = scope ?? throw new ArgumentNullException(nameof(scope), $"The parameter '{nameof(scope)}' cannot be null."); + } + } +} \ No newline at end of file diff --git a/src/OneGit.Api/Controllers/RepositoriesController.cs b/src/OneGit.Api/Controllers/RepositoriesController.cs new file mode 100644 index 0000000..c2b4a1a --- /dev/null +++ b/src/OneGit.Api/Controllers/RepositoriesController.cs @@ -0,0 +1,221 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using OneGit.Api.Data; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace OneGit.Api.Controllers +{ + [Route("api/[controller]")] + [Produces("application/json")] + public class RepositoriesController : Controller + { + private ILogger logger; + private readonly RepositoryContext context; + + public RepositoriesController(ILogger logger, RepositoryContext context) + { + this.logger = logger; + this.context = context; + } + + /// + /// List all repositories. + /// + /// + /// Sample request: + /// + /// GET api/repositories + /// Content-Type: application/json + /// authorization: Bearer {your-access-token-here} + /// { + /// } + /// + /// + /// A list of the repositories currently stored in a database. + /// Returns the The specified repository. + /// If the header 'authorization' is not specified. + [HttpGet] + [Authorize("read:repositories")] + [ProducesResponseType(200)] + [ProducesResponseType(401)] + public async Task> Get() + { + return await this.context.Repositories.AsNoTracking().ToListAsync(); + } + + /// + /// Gets the specified repository. + /// + /// + /// Sample request: + /// + /// GET api/repositories/12345678-90ab-cdef-1234-567890abcdef + /// Content-Type: application/json + /// authorization: Bearer {your-access-token-here} + /// { + /// } + /// + /// + /// The repository's identifier. + /// The specified repository. + /// Returns the The specified repository. + /// If the header 'authorization' is not specified. + /// If the repository hasn't been found by its identifier. + [HttpGet("{id}", Name = "GetRepository")] + [Authorize("read:repositories")] + [ProducesResponseType(200)] + [ProducesResponseType(401)] + [ProducesResponseType(404)] + public IActionResult Get(Guid id) + { + var repository = this.context.Repositories.Find(id); + if (repository == null) + { + return NotFound(); + } + + return new ObjectResult(repository); + } + + /// + /// Creates a new repository. + /// + /// + /// Sample request: + /// + /// POST api/repositories + /// Content-Type: application/json + /// authorization: Bearer {your-access-token-here} + /// { + /// "name": ".NET Standard", + /// "description": "This repo is building the .NET Standard", + /// "url": "https://github.com/dotnet/standard" + /// } + /// + /// + /// The new repository. + /// A newly created repository. + /// Returns the newly created repository. + /// If the item is null. + /// If the header 'authorization' is not specified. + [HttpPost] + [Authorize("create:repositories")] + [ProducesResponseType(201)] + [ProducesResponseType(400)] + [ProducesResponseType(401)] + public IActionResult Post([FromBody] RepositoryModel repository) + { + if (repository == null) + { + return BadRequest(); + } + + try + { + this.context.Repositories.Add(repository); + this.context.SaveChanges(); + } + catch (Exception ex) + { + this.logger.LogError($"An error occurred while creating entity '{nameof(RepositoryModel)}': {ex.ToString()}"); + } + + return CreatedAtRoute("GetRepository", new { id = repository.Id }, repository); + } + + /// + /// Updates the specified repository. + /// + /// + /// Sample request: + /// + /// PUT api/repositories/12345678-90ab-cdef-1234-567890abcdef + /// Content-Type: application/json + /// authorization: Bearer {your-access-token-here} + /// { + /// "name": "SignalR, + /// "description": "Incredibly simple real-time web for ASP.NET Core", + /// "url": "https://github.com/aspnet/SignalR" + /// } + /// + /// + /// The repository's identifier. + /// The updated repository. + /// No content. + /// If the specified repository has been sucessfully deleted. + /// If the item is null. + /// If the header 'authorization' is not specified. + [HttpPut("{id}")] + [Authorize("update:repositories")] + [ProducesResponseType(204)] + [ProducesResponseType(400)] + [ProducesResponseType(401)] + public async Task Put(Guid id, [FromBody] RepositoryModel repository) + { + if (repository == null || repository.Id != id) + { + return BadRequest(); + } + + try + { + this.context.Attach(repository).State = EntityState.Modified; + await this.context.SaveChangesAsync(); + } + catch (Exception ex) + { + this.logger.LogError($"An error occurred while updating the entity '{nameof(RepositoryModel)}' with id {id}: {ex.ToString()}"); + } + + return new NoContentResult(); + } + + /// + /// Deletes the specified repository. + /// + /// + /// Sample request: + /// + /// DELETE api/repositories/12345678-90ab-cdef-1234-567890abcdef + /// Content-Type: application/json + /// authorization: Bearer {your-access-token-here} + /// { + /// } + /// + /// + /// The repository's identifier. + /// No content. + /// If the specified repository has been sucessfully deleted. + /// If the header 'authorization' is not specified. + /// If the repository hasn't been found by its identifier. + [HttpDelete("{id}")] + [Authorize("delete:repositories")] + [ProducesResponseType(204)] + [ProducesResponseType(401)] + [ProducesResponseType(404)] + public async Task Delete(Guid id) + { + var repository = await this.context.Repositories.FindAsync(id); + if (repository == null) + { + return NotFound(); + } + + try + { + this.context.Repositories.Remove(repository); + await this.context.SaveChangesAsync(); + } + catch (Exception ex) + { + this.logger.LogError($"An error occurred while deleting the entity '{nameof(RepositoryModel)}' with id '{id}': {ex.ToString()}"); + } + + return new NoContentResult(); + } + } +} \ No newline at end of file diff --git a/src/OneGit/Data/Migrations/20180420161436_Initial.Designer.cs b/src/OneGit.Api/Data/Migrations/20180420161436_Initial.Designer.cs similarity index 93% rename from src/OneGit/Data/Migrations/20180420161436_Initial.Designer.cs rename to src/OneGit.Api/Data/Migrations/20180420161436_Initial.Designer.cs index a0fad5b..d5aa948 100644 --- a/src/OneGit/Data/Migrations/20180420161436_Initial.Designer.cs +++ b/src/OneGit.Api/Data/Migrations/20180420161436_Initial.Designer.cs @@ -1,5 +1,5 @@ // -using OneGit.Data; +using OneGit.Api.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -8,9 +8,9 @@ using Microsoft.EntityFrameworkCore.Storage.Internal; using System; -namespace OneGit.Migrations +namespace OneGit.Api.Migrations { - [DbContext(typeof(AppDbContext))] + [DbContext(typeof(RepositoryContext))] [Migration("20180420161436_Initial")] partial class Initial { diff --git a/src/OneGit/Data/Migrations/20180420161436_Initial.cs b/src/OneGit.Api/Data/Migrations/20180420161436_Initial.cs similarity index 90% rename from src/OneGit/Data/Migrations/20180420161436_Initial.cs rename to src/OneGit.Api/Data/Migrations/20180420161436_Initial.cs index 7c58328..ac71fc3 100644 --- a/src/OneGit/Data/Migrations/20180420161436_Initial.cs +++ b/src/OneGit.Api/Data/Migrations/20180420161436_Initial.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; -namespace OneGit.Migrations +namespace OneGit.Api.Migrations { public partial class Initial : Migration { @@ -12,14 +12,14 @@ protected override void Up(MigrationBuilder migrationBuilder) name: "Repositories", columns: table => new { - ID = table.Column(nullable: false), + Id = table.Column(nullable: false), Description = table.Column(maxLength: 256, nullable: true), Name = table.Column(maxLength: 50, nullable: false), Url = table.Column(maxLength: 100, nullable: true) }, constraints: table => { - table.PrimaryKey("PK_Repositories", x => x.ID); + table.PrimaryKey("PK_Repositories", x => x.Id); }); } diff --git a/src/OneGit/Data/Migrations/AppDbContextModelSnapshot.cs b/src/OneGit.Api/Data/Migrations/AppDbContextModelSnapshot.cs similarity index 87% rename from src/OneGit/Data/Migrations/AppDbContextModelSnapshot.cs rename to src/OneGit.Api/Data/Migrations/AppDbContextModelSnapshot.cs index 1900a05..0449646 100644 --- a/src/OneGit/Data/Migrations/AppDbContextModelSnapshot.cs +++ b/src/OneGit.Api/Data/Migrations/AppDbContextModelSnapshot.cs @@ -1,5 +1,5 @@ // -using OneGit.Data; +using OneGit.Api.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -8,9 +8,9 @@ using Microsoft.EntityFrameworkCore.Storage.Internal; using System; -namespace OneGit.Migrations +namespace OneGit.Api.Migrations { - [DbContext(typeof(AppDbContext))] + [DbContext(typeof(RepositoryContext))] partial class AppDbContextModelSnapshot : ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) @@ -22,7 +22,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Auth0.RepositoryModel", b => { - b.Property("ID") + b.Property("Id") .ValueGeneratedOnAdd(); b.Property("Description") @@ -35,7 +35,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Url") .HasMaxLength(100); - b.HasKey("ID"); + b.HasKey("Id"); b.ToTable("Repositories"); }); diff --git a/src/OneGit.Api/Data/RepositoryDbContext.cs b/src/OneGit.Api/Data/RepositoryDbContext.cs new file mode 100644 index 0000000..0a5562b --- /dev/null +++ b/src/OneGit.Api/Data/RepositoryDbContext.cs @@ -0,0 +1,13 @@ +using Microsoft.EntityFrameworkCore; + +namespace OneGit.Api.Data +{ + public class RepositoryContext : DbContext + { + public RepositoryContext(DbContextOptions options) : base(options) + { + } + + public DbSet Repositories { get; set; } + } +} \ No newline at end of file diff --git a/src/OneGit.Api/Dockerfile b/src/OneGit.Api/Dockerfile new file mode 100644 index 0000000..94ff5e8 --- /dev/null +++ b/src/OneGit.Api/Dockerfile @@ -0,0 +1,20 @@ +FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +WORKDIR /app +EXPOSE 55836 +EXPOSE 44359 + +FROM microsoft/dotnet:2.1-sdk AS build +WORKDIR /src +COPY OneGit.Api/OneGit.Api.csproj OneGit.Api/ +RUN dotnet restore OneGit.Api/OneGit.Api.csproj +COPY . . +WORKDIR /src/OneGit.Api +RUN dotnet build OneGit.Api.csproj -c Release -o /app + +FROM build AS publish +RUN dotnet publish OneGit.Api.csproj -c Release -o /app + +FROM base AS final +WORKDIR /app +COPY --from=publish /app . +ENTRYPOINT ["dotnet", "OneGit.Api.dll"] diff --git a/src/OneGit.Api/Models/RepositoryModel.cs b/src/OneGit.Api/Models/RepositoryModel.cs new file mode 100644 index 0000000..613dbfe --- /dev/null +++ b/src/OneGit.Api/Models/RepositoryModel.cs @@ -0,0 +1,20 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace OneGit.Api +{ + public class RepositoryModel + { + [Key] + public Guid Id { get; set; } + + [Required, StringLength(50)] + public string Name { get; set; } + + [StringLength(256)] + public string Description { get; set; } + + [Required, StringLength(100)] + public string Url { get; set; } + } +} \ No newline at end of file diff --git a/src/OneGit.Api/OneGit.Api.csproj b/src/OneGit.Api/OneGit.Api.csproj new file mode 100644 index 0000000..927bc49 --- /dev/null +++ b/src/OneGit.Api/OneGit.Api.csproj @@ -0,0 +1,31 @@ + + + + netcoreapp2.2 + bfe4ce45-dc32-44de-8b70-a210c0e19266 + Linux + + + + bin\Debug\netcoreapp2.1\OneGit.Api.xml + + + + bin\Release\netcoreapp2.1\OneGit.Api.xml + + + + + + + + + + + + + + + + + diff --git a/src/OneGit/Program.cs b/src/OneGit.Api/Program.cs similarity index 53% rename from src/OneGit/Program.cs rename to src/OneGit.Api/Program.cs index 22cf003..0117619 100644 --- a/src/OneGit/Program.cs +++ b/src/OneGit.Api/Program.cs @@ -1,18 +1,17 @@ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; -namespace OneGit +namespace OneGit.Api { public class Program { public static void Main(string[] args) { - BuildWebHost(args).Run(); + CreateWebHostBuilder(args).Build().Run(); } - public static IWebHost BuildWebHost(string[] args) => + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) - .UseStartup() - .Build(); + .UseStartup(); } } \ No newline at end of file diff --git a/src/OneGit.Api/Startup.cs b/src/OneGit.Api/Startup.cs new file mode 100644 index 0000000..c2237e4 --- /dev/null +++ b/src/OneGit.Api/Startup.cs @@ -0,0 +1,127 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using OneGit.Api.Authorization; +using OneGit.Api.Data; +using Swashbuckle.AspNetCore.Swagger; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; + +namespace OneGit.Api +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + this.Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddDbContext(options => + options.UseSqlServer(this.Configuration.GetConnectionString("DefaultConnection"))); + + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + + // 1. Add Authentication Services + string domain = $"https://{this.Configuration["Auth0:Domain"]}/"; + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(options => + { + options.Authority = domain; + options.Audience = this.Configuration["Auth0:ApiIdentifier"]; + }); + + services.AddAuthorization(options => + { + options.AddPolicy("read:repositories", policy => policy.Requirements.Add(new HasScopeRequirement(domain, "read:repositories"))); + options.AddPolicy("create:repositories", policy => policy.Requirements.Add(new HasScopeRequirement(domain, "create:repositories"))); + options.AddPolicy("update:repositories", policy => policy.Requirements.Add(new HasScopeRequirement(domain, "update:repositories"))); + options.AddPolicy("delete:repositories", policy => policy.Requirements.Add(new HasScopeRequirement(domain, "delete:repositories"))); + }); + + // register the scope authorization handler + services.AddSingleton(); + + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new Info + { + Version = "v1", + Title = "Repositories API", + Description = "A simple example ASP.NET Core Web API", + TermsOfService = "None", + Contact = new Contact + { + Name = "Oleg Burov", + Email = "oleg.burov@outlook.com", + Url = "https://twitter.com/oleg_burov" + }, + License = new License + { + Name = "MIT", + Url = "https://github.com/olegburov/Auth0/blob/master/LICENSE" + } + }); + + c.AddSecurityDefinition("JWT Bearer", new ApiKeyScheme + { + Description = @"Auth0 Access Token in the header 'Authorization' of HTTP request as a Bearer token. +

Example: 'Authorization: Bearer {Your-Auth0-access_token-here}'

", + Name = "Authorization", + In = "header", + Type = "apiKey" + }); + + c.AddSecurityRequirement(new Dictionary> + { + {"JWT Bearer", new string[] { }}, + }); + + // Set the comments path for the Swagger JSON and UI. + var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + c.IncludeXmlComments(xmlPath); + }); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseHsts(); + } + + app.UseAuthentication(); + + app.UseSwagger(); + app.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "Repositories API V1"); + c.RoutePrefix = string.Empty; + }); + + app.UseHttpsRedirection(); + app.UseMvc(); + } + } +} \ No newline at end of file diff --git a/src/OneGit.Api/appsettings.Development.json b/src/OneGit.Api/appsettings.Development.json new file mode 100644 index 0000000..ea10311 --- /dev/null +++ b/src/OneGit.Api/appsettings.Development.json @@ -0,0 +1,19 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + }, + + "Auth0": { + "Domain": "{your-domain-here}", + "ApiIdentifier": "{your-api-identifier-here}" + }, + + "ConnectionStrings": { + "DefaultConnection": "Server=.;Database=OneGit;Trusted_Connection=True;MultipleActiveResultSets=true" + } +} diff --git a/src/OneGit.Api/appsettings.json b/src/OneGit.Api/appsettings.json new file mode 100644 index 0000000..99b2bb8 --- /dev/null +++ b/src/OneGit.Api/appsettings.json @@ -0,0 +1,24 @@ +{ + "Logging": { + "IncludeScopes": false, + "Debug": { + "LogLevel": { + "Default": "Warning" + } + }, + "Console": { + "LogLevel": { + "Default": "Warning" + } + } + }, + + "Auth0": { + "Domain": "{your-domain-here}", + "ApiIdentifier": "{your-api-identifier-here}" + }, + + "ConnectionStrings": { + "DefaultConnection": "Server=.;Database=OneGit;Trusted_Connection=True;MultipleActiveResultSets=true" + } +} \ No newline at end of file diff --git a/src/OneGit/Controllers/AccountController.cs b/src/OneGit.Web/Controllers/AccountController.cs similarity index 84% rename from src/OneGit/Controllers/AccountController.cs rename to src/OneGit.Web/Controllers/AccountController.cs index dba2d4b..0a232a0 100644 --- a/src/OneGit/Controllers/AccountController.cs +++ b/src/OneGit.Web/Controllers/AccountController.cs @@ -2,11 +2,11 @@ using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using OneGit.Models; +using OneGit.Web.Models; using System.Linq; using System.Threading.Tasks; -namespace OneGit.Controllers +namespace OneGit.Web.Controllers { [Route("[controller]/[action]")] public class AccountController : Controller @@ -37,13 +37,15 @@ public async Task Signout() [HttpGet] [Authorize] - public IActionResult Profile() + public async Task Profile() { return View(new UserProfileModel() { Name = User.Identity.Name, EmailAddress = User.Claims.FirstOrDefault(c => c.Type == "name")?.Value, - ProfileImage = User.Claims.FirstOrDefault(c => c.Type == "picture")?.Value + ProfileImage = User.Claims.FirstOrDefault(c => c.Type == "picture")?.Value, + IdToken = await HttpContext.GetTokenAsync("id_token"), + AccessToken = await HttpContext.GetTokenAsync("access_token") }); } diff --git a/src/OneGit/Controllers/HomeController.cs b/src/OneGit.Web/Controllers/HomeController.cs similarity index 56% rename from src/OneGit/Controllers/HomeController.cs rename to src/OneGit.Web/Controllers/HomeController.cs index 7f99ce4..b2a4932 100644 --- a/src/OneGit/Controllers/HomeController.cs +++ b/src/OneGit.Web/Controllers/HomeController.cs @@ -1,28 +1,33 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using OneGit.Data; -using OneGit.Models; +using OneGit.Web.Models; +using OneGit.Web.Services; using System; using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; - -namespace OneGit.Controllers +namespace OneGit.Web.Controllers { public class HomeController : Controller { - private readonly AppDbContext appContext; + private RepositoryClient repositoryClient; - public HomeController(AppDbContext context) + public HomeController(RepositoryClient client) { - this.appContext = context; + this.repositoryClient = client; } public async Task Index() { - var repositoriesList = await this.appContext.Repositories.AsNoTracking().ToListAsync(); - return View(repositoriesList); + var repositories = Enumerable.Empty(); + + if (User.Identity.IsAuthenticated) + { + repositories = await this.repositoryClient.GetAllRepositoriesAsync(); + } + + return View(repositories); } [HttpGet] @@ -41,17 +46,7 @@ public async Task New(RepositoryModel repository) return View(); } - this.appContext.Add(repository); - - try - { - await this.appContext.SaveChangesAsync(); - } - catch (Exception ex) - { - ViewData["Alert"] = ex.Message; - return View(); - } + await this.repositoryClient.CreateNewRepositoryAsync(repository); return RedirectToAction("Index"); } @@ -60,13 +55,13 @@ public async Task New(RepositoryModel repository) [Authorize(Roles = "admin, editor")] public async Task Edit(Guid id) { - var repository = await this.appContext.Repositories.FindAsync(id); + var repository = await this.repositoryClient.GetRepositoryAsync(id); if (repository == null) { return RedirectToAction("Index"); } - + return View(repository); } @@ -79,16 +74,7 @@ public async Task Edit(RepositoryModel repository) return View(); } - try - { - this.appContext.Attach(repository).State = EntityState.Modified; - await this.appContext.SaveChangesAsync(); - } - catch (Exception ex) - { - ViewData["Alert"] = ex.Message; - return View(); - } + await this.repositoryClient.UpdateRepositoryAsync(repository); return RedirectToAction("Index"); } @@ -96,13 +82,7 @@ public async Task Edit(RepositoryModel repository) [Authorize(Roles = "admin")] public async Task Delete(Guid id) { - var repository = await this.appContext.Repositories.FindAsync(id); - - if (repository != null) - { - this.appContext.Remove(repository); - await this.appContext.SaveChangesAsync(); - } + await this.repositoryClient.DeleteRepositoryAsync(id); return RedirectToAction("Index"); } diff --git a/src/OneGit.Web/Dockerfile b/src/OneGit.Web/Dockerfile new file mode 100644 index 0000000..6056a7b --- /dev/null +++ b/src/OneGit.Web/Dockerfile @@ -0,0 +1,20 @@ +FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base +WORKDIR /app +EXPOSE 43163 +EXPOSE 44399 + +FROM microsoft/dotnet:2.1-sdk AS build +WORKDIR /src +COPY OneGit.Web/OneGit.Web.csproj OneGit.Web/ +RUN dotnet restore OneGit.Web/OneGit.Web.csproj +COPY . . +WORKDIR /src/OneGit.Web +RUN dotnet build OneGit.Web.csproj -c Release -o /app + +FROM build AS publish +RUN dotnet publish OneGit.Web.csproj -c Release -o /app + +FROM base AS final +WORKDIR /app +COPY --from=publish /app . +ENTRYPOINT ["dotnet", "OneGit.Web.dll"] diff --git a/src/OneGit/Models/ErrorViewModel.cs b/src/OneGit.Web/Models/ErrorViewModel.cs similarity index 84% rename from src/OneGit/Models/ErrorViewModel.cs rename to src/OneGit.Web/Models/ErrorViewModel.cs index e7a1696..1847b8d 100644 --- a/src/OneGit/Models/ErrorViewModel.cs +++ b/src/OneGit.Web/Models/ErrorViewModel.cs @@ -1,4 +1,4 @@ -namespace OneGit.Models +namespace OneGit.Web.Models { public class ErrorViewModel { diff --git a/src/OneGit/Models/RepositoryModel.cs b/src/OneGit.Web/Models/RepositoryModel.cs similarity index 85% rename from src/OneGit/Models/RepositoryModel.cs rename to src/OneGit.Web/Models/RepositoryModel.cs index a2574ba..159e33c 100644 --- a/src/OneGit/Models/RepositoryModel.cs +++ b/src/OneGit.Web/Models/RepositoryModel.cs @@ -1,11 +1,11 @@ using System; using System.ComponentModel.DataAnnotations; -namespace OneGit +namespace OneGit.Web { public class RepositoryModel { - public Guid ID { get; set; } + public Guid Id { get; set; } [Required, StringLength(50)] public string Name { get; set; } diff --git a/src/OneGit/Models/UserProfileModel.cs b/src/OneGit.Web/Models/UserProfileModel.cs similarity index 59% rename from src/OneGit/Models/UserProfileModel.cs rename to src/OneGit.Web/Models/UserProfileModel.cs index e733108..6135af5 100644 --- a/src/OneGit/Models/UserProfileModel.cs +++ b/src/OneGit.Web/Models/UserProfileModel.cs @@ -1,4 +1,4 @@ -namespace OneGit.Models +namespace OneGit.Web.Models { public class UserProfileModel { @@ -7,5 +7,9 @@ public class UserProfileModel public string Name { get; set; } public string ProfileImage { get; set; } + + public string IdToken { get; set; } + + public string AccessToken { get; set; } } } \ No newline at end of file diff --git a/src/OneGit.Web/OneGit.Web.csproj b/src/OneGit.Web/OneGit.Web.csproj new file mode 100644 index 0000000..2910fb5 --- /dev/null +++ b/src/OneGit.Web/OneGit.Web.csproj @@ -0,0 +1,18 @@ + + + + netcoreapp2.2 + fc544d68-3a59-46ef-bb96-c511696b3ce3 + Linux + + + + + + + + + + + + diff --git a/src/OneGit.Web/Program.cs b/src/OneGit.Web/Program.cs new file mode 100644 index 0000000..24aaa86 --- /dev/null +++ b/src/OneGit.Web/Program.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; + +namespace OneGit.Web +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} \ No newline at end of file diff --git a/src/OneGit.Web/Services/RepositoryClient.cs b/src/OneGit.Web/Services/RepositoryClient.cs new file mode 100644 index 0000000..f40c8d8 --- /dev/null +++ b/src/OneGit.Web/Services/RepositoryClient.cs @@ -0,0 +1,114 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; + +namespace OneGit.Web.Services +{ + public class RepositoryClient + { + private HttpClient client; + private ILogger logger; + private IHttpContextAccessor httpContextAccessor; + + public RepositoryClient(HttpClient client, ILogger logger, IHttpContextAccessor httpContextAccessor) + { + this.client = client; + this.logger = logger; + this.httpContextAccessor = httpContextAccessor; + + var context = this.httpContextAccessor.HttpContext; + var token = context.GetTokenAsync("access_token").Result; + + if (token != null) + { + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + } + } + + public async Task> GetAllRepositoriesAsync() + { + try + { + var response = await client.GetAsync("api/repositories"); + response.EnsureSuccessStatusCode(); + + return await response.Content.ReadAsAsync>(); + } + catch (HttpRequestException ex) + { + logger.LogError($"An error occured connecting to values API {ex.ToString()}"); + return Enumerable.Empty(); + } + } + + public async Task GetRepositoryAsync(Guid id) + { + try + { + var response = await client.GetAsync($"api/repositories/{id}"); + response.EnsureSuccessStatusCode(); + + return await response.Content.ReadAsAsync(); + } + catch (HttpRequestException ex) + { + logger.LogError($"An error occured connecting to values API {ex.ToString()}"); + return null; + } + } + + public async Task CreateNewRepositoryAsync(RepositoryModel repository) + { + try + { + var response = await client.PostAsJsonAsync("api/repositories", repository); + response.EnsureSuccessStatusCode(); + + return; + } + catch (HttpRequestException ex) + { + logger.LogError($"An error occured connecting to values API {ex.ToString()}"); + return; + } + } + + public async Task UpdateRepositoryAsync(RepositoryModel repository) + { + try + { + var response = await client.PutAsJsonAsync($"api/repositories/{repository.Id}", repository); + response.EnsureSuccessStatusCode(); + + return; + } + catch (HttpRequestException ex) + { + logger.LogError($"An error occured connecting to values API {ex.ToString()}"); + return; + } + } + + public async Task DeleteRepositoryAsync(Guid id) + { + try + { + var response = await client.DeleteAsync($"api/repositories/{id}"); + response.EnsureSuccessStatusCode(); + + return; + } + catch (HttpRequestException ex) + { + logger.LogError($"An error occured connecting to values API {ex.ToString()}"); + return; + } + } + } +} \ No newline at end of file diff --git a/src/OneGit/Startup.cs b/src/OneGit.Web/Startup.cs similarity index 67% rename from src/OneGit/Startup.cs rename to src/OneGit.Web/Startup.cs index 9aaa110..8e330f9 100644 --- a/src/OneGit/Startup.cs +++ b/src/OneGit.Web/Startup.cs @@ -1,23 +1,24 @@ -using OneGit.Data; -using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; -using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; +using OneGit.Web.Services; using System; +using System.Linq; using System.Threading.Tasks; -namespace OneGit +namespace OneGit.Web { public class Startup { public Startup(IConfiguration configuration) { - Configuration = configuration; + this.Configuration = configuration; } public IConfiguration Configuration { get; } @@ -25,25 +26,29 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddDbContext(options => - options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); + services.Configure(options => + { + // This lambda determines whether user consent for non-essential cookies is needed for a given request. + options.CheckConsentNeeded = context => true; + options.MinimumSameSitePolicy = SameSiteMode.None; + }); // Add authentication services services.AddAuthentication(options => { options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) .AddCookie(options => options.LoginPath = "/Account/Signin") .AddOpenIdConnect("Auth0", options => - { + { // Set the authority to your Auth0 domain - options.Authority = $"https://{Configuration["Auth0:Domain"]}"; + options.Authority = $"https://{this.Configuration["Auth0:Domain"]}"; // Configure the Auth0 Client ID and Client Secret - options.ClientId = Configuration["Auth0:ClientId"]; - options.ClientSecret = Configuration["Auth0:ClientSecret"]; + options.ClientId = this.Configuration["Auth0:ClientId"]; + options.ClientSecret = this.Configuration["Auth0:ClientSecret"]; // Set response type to code options.ResponseType = "code"; @@ -54,17 +59,20 @@ public void ConfigureServices(IServiceCollection services) options.Scope.Add("profile"); options.Scope.Add("email"); + var apiScopes = string.Join(" ", this.Configuration.GetSection("Auth0:ApiScopes").GetChildren().Select(s => s.Value)); + options.Scope.Add(apiScopes); + // Set the correct name claim type options.TokenValidationParameters = new TokenValidationParameters { NameClaimType = "nickname", - RoleClaimType = "https://schemas.quickstarts.com/roles" + RoleClaimType = "https://olegburov.com/roles" }; // Set the callback path, so Auth0 will call back to http://localhost:5000/signin-auth0 // Also ensure that you have added the URL as an Allowed Callback URL in your Auth0 dashboard options.CallbackPath = new PathString("/signin-auth0"); - + // Configure the Claims Issuer to be Auth0 options.ClaimsIssuer = "Auth0"; @@ -76,7 +84,7 @@ public void ConfigureServices(IServiceCollection services) // handle the logout redirection OnRedirectToIdentityProviderForSignOut = (context) => { - var logoutUri = $"https://{Configuration["Auth0:Domain"]}/v2/logout?client_id={Configuration["Auth0:ClientId"]}"; + var logoutUri = $"https://{this.Configuration["Auth0:Domain"]}/v2/logout?client_id={this.Configuration["Auth0:ClientId"]}"; var postLogoutUri = context.Properties.RedirectUri; if (!string.IsNullOrEmpty(postLogoutUri)) @@ -95,12 +103,23 @@ public void ConfigureServices(IServiceCollection services) context.HandleResponse(); return Task.CompletedTask; - } + }, + + OnRedirectToIdentityProvider = context => + { + context.ProtocolMessage.SetParameter("audience", this.Configuration["Auth0:ApiIdentifier"]); + + return Task.FromResult(0); + } }; }); // Add framework services. - services.AddMvc(); + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + + services.AddSingleton(); + + services.AddHttpClient(client => client.BaseAddress = new Uri(this.Configuration["Auth0:ApiBaseUrl"])); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -108,15 +127,17 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { - app.UseBrowserLink(); app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); + app.UseHsts(); } + app.UseHttpsRedirection(); app.UseStaticFiles(); + app.UseCookiePolicy(); app.UseAuthentication(); @@ -128,4 +149,4 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env) }); } } -} +} \ No newline at end of file diff --git a/src/OneGit/Views/Account/AccessDenied.cshtml b/src/OneGit.Web/Views/Account/AccessDenied.cshtml similarity index 95% rename from src/OneGit/Views/Account/AccessDenied.cshtml rename to src/OneGit.Web/Views/Account/AccessDenied.cshtml index e626eef..57b0987 100644 --- a/src/OneGit/Views/Account/AccessDenied.cshtml +++ b/src/OneGit.Web/Views/Account/AccessDenied.cshtml @@ -1,5 +1,4 @@ - -@{ +@{ ViewData["Title"] = "Access Denied"; } diff --git a/src/OneGit.Web/Views/Account/Profile.cshtml b/src/OneGit.Web/Views/Account/Profile.cshtml new file mode 100644 index 0000000..b76b370 --- /dev/null +++ b/src/OneGit.Web/Views/Account/Profile.cshtml @@ -0,0 +1,24 @@ +@model OneGit.Web.Models.UserProfileModel +@{ + ViewData["Title"] = "Profile"; +} + +

@ViewData["Title"]

+ +@Model.Name +

@Model.Name

+

+ @Model.EmailAddress +

+

+ id_token +

+ @Model.IdToken +
+

+

+ access_token +

+ @Model.AccessToken +
+

\ No newline at end of file diff --git a/src/OneGit/Views/Home/Edit.cshtml b/src/OneGit.Web/Views/Home/Edit.cshtml similarity index 94% rename from src/OneGit/Views/Home/Edit.cshtml rename to src/OneGit.Web/Views/Home/Edit.cshtml index e180497..4483b94 100644 --- a/src/OneGit/Views/Home/Edit.cshtml +++ b/src/OneGit.Web/Views/Home/Edit.cshtml @@ -1,5 +1,4 @@ -@model OneGit.RepositoryModel - +@model OneGit.Web.RepositoryModel @{ ViewData["Title"] = "Edit"; } @@ -20,7 +19,7 @@
- +
diff --git a/src/OneGit/Views/Home/Index.cshtml b/src/OneGit.Web/Views/Home/Index.cshtml similarity index 94% rename from src/OneGit/Views/Home/Index.cshtml rename to src/OneGit.Web/Views/Home/Index.cshtml index 60b8dfa..d1672b8 100644 --- a/src/OneGit/Views/Home/Index.cshtml +++ b/src/OneGit.Web/Views/Home/Index.cshtml @@ -1,5 +1,4 @@ -@model IEnumerable - +@model IEnumerable @{ ViewData["Title"] = "Home Page"; } @@ -82,7 +81,7 @@ else @if (!string.IsNullOrWhiteSpace(item.Description)) { - Html.DisplayFor(modelItem => item.Description); + @Html.DisplayFor(modelItem => item.Description); } else { @@ -93,11 +92,11 @@ else
@if (User.IsInRole("admin") || User.IsInRole("editor")) { - Edit + Edit } @if (User.IsInRole("admin")) { - Delete + Delete }
diff --git a/src/OneGit/Views/Home/New.cshtml b/src/OneGit.Web/Views/Home/New.cshtml similarity index 97% rename from src/OneGit/Views/Home/New.cshtml rename to src/OneGit.Web/Views/Home/New.cshtml index c514948..625c2e9 100644 --- a/src/OneGit/Views/Home/New.cshtml +++ b/src/OneGit.Web/Views/Home/New.cshtml @@ -1,5 +1,4 @@ -@model OneGit.RepositoryModel - +@model OneGit.Web.RepositoryModel @{ ViewData["Title"] = "New"; } diff --git a/src/OneGit/Views/Shared/Error.cshtml b/src/OneGit.Web/Views/Shared/Error.cshtml similarity index 100% rename from src/OneGit/Views/Shared/Error.cshtml rename to src/OneGit.Web/Views/Shared/Error.cshtml diff --git a/src/OneGit/Views/Shared/_Layout.cshtml b/src/OneGit.Web/Views/Shared/_Layout.cshtml similarity index 100% rename from src/OneGit/Views/Shared/_Layout.cshtml rename to src/OneGit.Web/Views/Shared/_Layout.cshtml diff --git a/src/OneGit/Views/Shared/_LoginPartial.cshtml b/src/OneGit.Web/Views/Shared/_LoginPartial.cshtml similarity index 92% rename from src/OneGit/Views/Shared/_LoginPartial.cshtml rename to src/OneGit.Web/Views/Shared/_LoginPartial.cshtml index fd3b207..9b33cca 100644 --- a/src/OneGit/Views/Shared/_LoginPartial.cshtml +++ b/src/OneGit.Web/Views/Shared/_LoginPartial.cshtml @@ -8,7 +8,7 @@
    • diff --git a/src/OneGit/Views/Shared/_ValidationScriptsPartial.cshtml b/src/OneGit.Web/Views/Shared/_ValidationScriptsPartial.cshtml similarity index 100% rename from src/OneGit/Views/Shared/_ValidationScriptsPartial.cshtml rename to src/OneGit.Web/Views/Shared/_ValidationScriptsPartial.cshtml diff --git a/src/OneGit/Views/_ViewImports.cshtml b/src/OneGit.Web/Views/_ViewImports.cshtml similarity index 72% rename from src/OneGit/Views/_ViewImports.cshtml rename to src/OneGit.Web/Views/_ViewImports.cshtml index cc99021..940902a 100644 --- a/src/OneGit/Views/_ViewImports.cshtml +++ b/src/OneGit.Web/Views/_ViewImports.cshtml @@ -1,3 +1,3 @@ @using OneGit -@using OneGit.Models +@using OneGit.Web.Models @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers \ No newline at end of file diff --git a/src/OneGit/Views/_ViewStart.cshtml b/src/OneGit.Web/Views/_ViewStart.cshtml similarity index 100% rename from src/OneGit/Views/_ViewStart.cshtml rename to src/OneGit.Web/Views/_ViewStart.cshtml diff --git a/src/OneGit/appsettings.Development.json b/src/OneGit.Web/appsettings.Development.json similarity index 54% rename from src/OneGit/appsettings.Development.json rename to src/OneGit.Web/appsettings.Development.json index d91d046..4b9b0ec 100644 --- a/src/OneGit/appsettings.Development.json +++ b/src/OneGit.Web/appsettings.Development.json @@ -12,10 +12,9 @@ "Domain": "{your-domain-here}", "ClientId": "{your-client-id-here}", "ClientSecret": "{your-client-secret-here}", - "CallbackUrl": "http://localhost:60856/signin-auth0" - }, - - "ConnectionStrings": { - "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-Auth0;Trusted_Connection=True;MultipleActiveResultSets=true" + "CallbackUrl": "https://localhost:44399/signin-auth0", + "ApiBaseUrl": "https://localhost:44359/", + "ApiIdentifier": "{your-api-identifier-here}", + "ApiScopes": [ "read:repositories", "create:repositories", "update:repositories", "delete:repositories" ] } } diff --git a/src/OneGit.Web/appsettings.json b/src/OneGit.Web/appsettings.json new file mode 100644 index 0000000..d42d556 --- /dev/null +++ b/src/OneGit.Web/appsettings.json @@ -0,0 +1,18 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Warning" + } + }, + + "Auth0": { + "Domain": "{your-domain-here}", + "ClientId": "{your-client-id-here}", + "ClientSecret": "{your-client-secret-here}", + "CallbackUrl": "https://localhost:44399/signin-auth0", + "ApiBaseUrl": "https://localhost:44359/", + "ApiIdentifier": "{your-api-identifier-here}", + "ApiScopes": [ "read:repositories", "create:repositories", "update:repositories", "delete:repositories" ] + } +} \ No newline at end of file diff --git a/src/OneGit/bundleconfig.json b/src/OneGit.Web/bundleconfig.json similarity index 100% rename from src/OneGit/bundleconfig.json rename to src/OneGit.Web/bundleconfig.json diff --git a/src/OneGit/wwwroot/css/site.css b/src/OneGit.Web/wwwroot/css/site.css similarity index 100% rename from src/OneGit/wwwroot/css/site.css rename to src/OneGit.Web/wwwroot/css/site.css diff --git a/src/OneGit/wwwroot/css/site.min.css b/src/OneGit.Web/wwwroot/css/site.min.css similarity index 100% rename from src/OneGit/wwwroot/css/site.min.css rename to src/OneGit.Web/wwwroot/css/site.min.css diff --git a/src/OneGit/wwwroot/favicon.ico b/src/OneGit.Web/wwwroot/favicon.ico similarity index 100% rename from src/OneGit/wwwroot/favicon.ico rename to src/OneGit.Web/wwwroot/favicon.ico diff --git a/src/OneGit/wwwroot/images/auth0.svg b/src/OneGit.Web/wwwroot/images/auth0.svg similarity index 100% rename from src/OneGit/wwwroot/images/auth0.svg rename to src/OneGit.Web/wwwroot/images/auth0.svg diff --git a/src/OneGit/wwwroot/images/git.svg b/src/OneGit.Web/wwwroot/images/git.svg similarity index 100% rename from src/OneGit/wwwroot/images/git.svg rename to src/OneGit.Web/wwwroot/images/git.svg diff --git a/src/OneGit/wwwroot/js/site.js b/src/OneGit.Web/wwwroot/js/site.js similarity index 100% rename from src/OneGit/wwwroot/js/site.js rename to src/OneGit.Web/wwwroot/js/site.js diff --git a/src/OneGit/wwwroot/js/site.min.js b/src/OneGit.Web/wwwroot/js/site.min.js similarity index 100% rename from src/OneGit/wwwroot/js/site.min.js rename to src/OneGit.Web/wwwroot/js/site.min.js diff --git a/src/OneGit/wwwroot/lib/bootstrap/.bower.json b/src/OneGit.Web/wwwroot/lib/bootstrap/.bower.json similarity index 100% rename from src/OneGit/wwwroot/lib/bootstrap/.bower.json rename to src/OneGit.Web/wwwroot/lib/bootstrap/.bower.json diff --git a/src/OneGit/wwwroot/lib/bootstrap/LICENSE b/src/OneGit.Web/wwwroot/lib/bootstrap/LICENSE similarity index 100% rename from src/OneGit/wwwroot/lib/bootstrap/LICENSE rename to src/OneGit.Web/wwwroot/lib/bootstrap/LICENSE diff --git a/src/OneGit/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css b/src/OneGit.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css similarity index 100% rename from src/OneGit/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css rename to src/OneGit.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css diff --git a/src/OneGit/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map b/src/OneGit.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map similarity index 100% rename from src/OneGit/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map rename to src/OneGit.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map diff --git a/src/OneGit/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css b/src/OneGit.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css similarity index 100% rename from src/OneGit/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css rename to src/OneGit.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css diff --git a/src/OneGit/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map b/src/OneGit.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map similarity index 100% rename from src/OneGit/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map rename to src/OneGit.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map diff --git a/src/OneGit/wwwroot/lib/bootstrap/dist/css/bootstrap.css b/src/OneGit.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.css similarity index 100% rename from src/OneGit/wwwroot/lib/bootstrap/dist/css/bootstrap.css rename to src/OneGit.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.css diff --git a/src/OneGit/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map b/src/OneGit.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map similarity index 100% rename from src/OneGit/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map rename to src/OneGit.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map diff --git a/src/OneGit/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css b/src/OneGit.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css similarity index 100% rename from src/OneGit/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css rename to src/OneGit.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css diff --git a/src/OneGit/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map b/src/OneGit.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map similarity index 100% rename from src/OneGit/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map rename to src/OneGit.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map diff --git a/src/OneGit/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot b/src/OneGit.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot similarity index 100% rename from src/OneGit/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot rename to src/OneGit.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot diff --git a/src/OneGit/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg b/src/OneGit.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg similarity index 100% rename from src/OneGit/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg rename to src/OneGit.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg diff --git a/src/OneGit/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf b/src/OneGit.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf similarity index 100% rename from src/OneGit/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf rename to src/OneGit.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf diff --git a/src/OneGit/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff b/src/OneGit.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff similarity index 100% rename from src/OneGit/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff rename to src/OneGit.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff diff --git a/src/OneGit/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 b/src/OneGit.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 similarity index 100% rename from src/OneGit/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 rename to src/OneGit.Web/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 diff --git a/src/OneGit/wwwroot/lib/bootstrap/dist/js/bootstrap.js b/src/OneGit.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.js similarity index 100% rename from src/OneGit/wwwroot/lib/bootstrap/dist/js/bootstrap.js rename to src/OneGit.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.js diff --git a/src/OneGit/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js b/src/OneGit.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js similarity index 100% rename from src/OneGit/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js rename to src/OneGit.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js diff --git a/src/OneGit/wwwroot/lib/bootstrap/dist/js/npm.js b/src/OneGit.Web/wwwroot/lib/bootstrap/dist/js/npm.js similarity index 100% rename from src/OneGit/wwwroot/lib/bootstrap/dist/js/npm.js rename to src/OneGit.Web/wwwroot/lib/bootstrap/dist/js/npm.js diff --git a/src/OneGit/wwwroot/lib/jquery-validation-unobtrusive/.bower.json b/src/OneGit.Web/wwwroot/lib/jquery-validation-unobtrusive/.bower.json similarity index 100% rename from src/OneGit/wwwroot/lib/jquery-validation-unobtrusive/.bower.json rename to src/OneGit.Web/wwwroot/lib/jquery-validation-unobtrusive/.bower.json diff --git a/src/OneGit/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js b/src/OneGit.Web/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js similarity index 100% rename from src/OneGit/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js rename to src/OneGit.Web/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js diff --git a/src/OneGit/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js b/src/OneGit.Web/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js similarity index 100% rename from src/OneGit/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js rename to src/OneGit.Web/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js diff --git a/src/OneGit/wwwroot/lib/jquery-validation/.bower.json b/src/OneGit.Web/wwwroot/lib/jquery-validation/.bower.json similarity index 100% rename from src/OneGit/wwwroot/lib/jquery-validation/.bower.json rename to src/OneGit.Web/wwwroot/lib/jquery-validation/.bower.json diff --git a/src/OneGit/wwwroot/lib/jquery-validation/LICENSE.md b/src/OneGit.Web/wwwroot/lib/jquery-validation/LICENSE.md similarity index 100% rename from src/OneGit/wwwroot/lib/jquery-validation/LICENSE.md rename to src/OneGit.Web/wwwroot/lib/jquery-validation/LICENSE.md diff --git a/src/OneGit/wwwroot/lib/jquery-validation/dist/additional-methods.js b/src/OneGit.Web/wwwroot/lib/jquery-validation/dist/additional-methods.js similarity index 100% rename from src/OneGit/wwwroot/lib/jquery-validation/dist/additional-methods.js rename to src/OneGit.Web/wwwroot/lib/jquery-validation/dist/additional-methods.js diff --git a/src/OneGit/wwwroot/lib/jquery-validation/dist/additional-methods.min.js b/src/OneGit.Web/wwwroot/lib/jquery-validation/dist/additional-methods.min.js similarity index 100% rename from src/OneGit/wwwroot/lib/jquery-validation/dist/additional-methods.min.js rename to src/OneGit.Web/wwwroot/lib/jquery-validation/dist/additional-methods.min.js diff --git a/src/OneGit/wwwroot/lib/jquery-validation/dist/jquery.validate.js b/src/OneGit.Web/wwwroot/lib/jquery-validation/dist/jquery.validate.js similarity index 100% rename from src/OneGit/wwwroot/lib/jquery-validation/dist/jquery.validate.js rename to src/OneGit.Web/wwwroot/lib/jquery-validation/dist/jquery.validate.js diff --git a/src/OneGit/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js b/src/OneGit.Web/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js similarity index 100% rename from src/OneGit/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js rename to src/OneGit.Web/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js diff --git a/src/OneGit/wwwroot/lib/jquery/.bower.json b/src/OneGit.Web/wwwroot/lib/jquery/.bower.json similarity index 100% rename from src/OneGit/wwwroot/lib/jquery/.bower.json rename to src/OneGit.Web/wwwroot/lib/jquery/.bower.json diff --git a/src/OneGit/wwwroot/lib/jquery/LICENSE.txt b/src/OneGit.Web/wwwroot/lib/jquery/LICENSE.txt similarity index 100% rename from src/OneGit/wwwroot/lib/jquery/LICENSE.txt rename to src/OneGit.Web/wwwroot/lib/jquery/LICENSE.txt diff --git a/src/OneGit/wwwroot/lib/jquery/dist/jquery.js b/src/OneGit.Web/wwwroot/lib/jquery/dist/jquery.js similarity index 100% rename from src/OneGit/wwwroot/lib/jquery/dist/jquery.js rename to src/OneGit.Web/wwwroot/lib/jquery/dist/jquery.js diff --git a/src/OneGit/wwwroot/lib/jquery/dist/jquery.min.js b/src/OneGit.Web/wwwroot/lib/jquery/dist/jquery.min.js similarity index 100% rename from src/OneGit/wwwroot/lib/jquery/dist/jquery.min.js rename to src/OneGit.Web/wwwroot/lib/jquery/dist/jquery.min.js diff --git a/src/OneGit/wwwroot/lib/jquery/dist/jquery.min.map b/src/OneGit.Web/wwwroot/lib/jquery/dist/jquery.min.map similarity index 100% rename from src/OneGit/wwwroot/lib/jquery/dist/jquery.min.map rename to src/OneGit.Web/wwwroot/lib/jquery/dist/jquery.min.map diff --git a/src/OneGit/Data/AppDbContext.cs b/src/OneGit/Data/AppDbContext.cs deleted file mode 100644 index 1c477a5..0000000 --- a/src/OneGit/Data/AppDbContext.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace OneGit.Data -{ - public class AppDbContext : DbContext - { - public AppDbContext(DbContextOptions options) : base(options) - { - } - - public DbSet Repositories { get; set; } - } -} \ No newline at end of file diff --git a/src/OneGit/Extensions/Auth0Settings.cs b/src/OneGit/Extensions/Auth0Settings.cs deleted file mode 100644 index 3460bba..0000000 --- a/src/OneGit/Extensions/Auth0Settings.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace OneGit.Extensions -{ - public class Auth0Settings - { - public string Domain { get; set; } - - public string CallbackUrl { get; set; } - - public string ClientId { get; set; } - - public string ClientSecret { get; set; } - } -} \ No newline at end of file diff --git a/src/OneGit/OneGit.csproj b/src/OneGit/OneGit.csproj deleted file mode 100644 index d75d299..0000000 --- a/src/OneGit/OneGit.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - netcoreapp2.0 - fc544d68-3a59-46ef-bb96-c511696b3ce3 - - - - - - - - - - - - diff --git a/src/OneGit/Views/Account/Profile.cshtml b/src/OneGit/Views/Account/Profile.cshtml deleted file mode 100644 index 02682c6..0000000 --- a/src/OneGit/Views/Account/Profile.cshtml +++ /dev/null @@ -1,13 +0,0 @@ -@model OneGit.Models.UserProfileModel - -@{ - ViewData["Title"] = "Profile"; -} - -

      @ViewData["Title"]

      - -@Model.Name -

      @Model.Name

      -

      - @Model.EmailAddress -

      \ No newline at end of file diff --git a/src/OneGit/appsettings.json b/src/OneGit/appsettings.json deleted file mode 100644 index 38554e6..0000000 --- a/src/OneGit/appsettings.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Warning" - } - }, - - "Auth0": { - "Domain": "{your-domain-here}", - "ClientId": "{your-client-id-here}", - "ClientSecret": "{your-client-secret-here}", - "CallbackUrl": "https://oleg-auth0.azurewebsites.net/signin-auth0" - }, - - "ConnectionStrings": { - "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-Auth0;Trusted_Connection=True;MultipleActiveResultSets=true" - } -} \ No newline at end of file

AltStyle によって変換されたページ (->オリジナル) /