Browse Source

Fixed migration issues and cleaned up startup

pull/111/head
Teknikode 1 year ago
parent
commit
852f17c2ac
  1. 15
      Teknik/Areas/Error/Controllers/ErrorController.cs
  2. 128
      Teknik/Middleware/BlacklistMiddleware.cs
  3. 73
      Teknik/Middleware/CORSMiddleware.cs
  4. 93
      Teknik/Middleware/ErrorHandlerMiddleware.cs
  5. 68
      Teknik/Middleware/PerformanceMonitorMiddleware.cs
  6. 53
      Teknik/Middleware/SecurityHeadersMiddleware.cs
  7. 42
      Teknik/Middleware/SetupHttpContextMiddleware.cs
  8. 16
      Teknik/Program.cs
  9. 47
      Teknik/Startup.cs
  10. 5
      Teknik/Teknik.csproj
  11. 4
      Teknik/package.json

15
Teknik/Areas/Error/Controllers/ErrorController.cs

@ -18,12 +18,13 @@ using Microsoft.AspNetCore.Http.Extensions; @@ -18,12 +18,13 @@ using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Diagnostics;
using Teknik.Data;
using Teknik.Logging;
using Teknik.WebCommon;
namespace Teknik.Areas.Error.Controllers
{
[Authorize]
[Area("Error")]
public class ErrorController : DefaultController
public class ErrorController : DefaultController, IErrorController
{
public ErrorController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
@ -31,6 +32,16 @@ namespace Teknik.Areas.Error.Controllers @@ -31,6 +32,16 @@ namespace Teknik.Areas.Error.Controllers
[TrackPageView]
public IActionResult HttpError(int statusCode)
{
return HttpError(statusCode, null);
}
[AllowAnonymous]
[TrackPageView]
public IActionResult HttpError(int statusCode, Exception ex)
{
if (ex != null)
return Http500(ex);
switch (statusCode)
{
case 401:
@ -39,6 +50,8 @@ namespace Teknik.Areas.Error.Controllers @@ -39,6 +50,8 @@ namespace Teknik.Areas.Error.Controllers
return Http403();
case 404:
return Http404();
case 500:
return Http500(ex);
default:
return HttpGeneral(statusCode);
}

128
Teknik/Middleware/BlacklistMiddleware.cs

@ -1,128 +0,0 @@ @@ -1,128 +0,0 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Teknik.Configuration;
namespace Teknik.Middleware
{
public class BlacklistMiddleware
{
private readonly RequestDelegate _next;
private readonly IMemoryCache _cache;
public BlacklistMiddleware(RequestDelegate next, IMemoryCache cache)
{
_next = next;
_cache = cache;
}
public async Task Invoke(HttpContext context, Config config)
{
// Beggining of Request
bool blocked = false;
string blockReason = string.Empty;
#region Detect Blacklisted IPs
if (!blocked)
{
string IPAddr = context.Request.HttpContext.Connection.RemoteIpAddress.ToString();
if (!string.IsNullOrEmpty(IPAddr))
{
StringDictionary badIPs = GetFileData(context, "BlockedIPs", config.IPBlacklistFile);
blocked |= (badIPs != null && badIPs.ContainsKey(IPAddr));
blockReason = $"This IP address ({IPAddr}) has been blacklisted. If you feel this is in error, please contact support@teknik.io for assistance.";
}
}
#endregion
#region Detect Blacklisted Referrers
if (!blocked)
{
string referrer = context.Request.Headers["Referer"].ToString();
string referrerHost = referrer;
try
{
var referrerUri = new Uri(referrer);
referrerHost = referrerUri.Host;
} catch
{ }
if (!string.IsNullOrEmpty(referrer))
{
StringDictionary badReferrers = GetFileData(context, "BlockedReferrers", config.ReferrerBlacklistFile);
if (badReferrers != null)
{
blocked |= badReferrers.ContainsKey(referrer) || badReferrers.ContainsKey(referrerHost);
blockReason = $"This referrer ({referrer}) has been blacklisted. If you feel this is in error, please contact support@teknik.io for assistance.";
}
}
}
#endregion
if (blocked)
{
// Clear the response
context.Response.Clear();
string jsonResult = JsonConvert.SerializeObject(new { error = new { type = "Blacklist", message = blockReason } });
await context.Response.WriteAsync(jsonResult);
return;
}
await _next.Invoke(context);
// End of request
}
public StringDictionary GetFileData(HttpContext context, string key, string filePath)
{
StringDictionary data;
if (!_cache.TryGetValue(key, out data))
{
data = GetFileLines(filePath);
_cache.Set(key, data);
}
return data;
}
public StringDictionary GetFileLines(string configPath)
{
StringDictionary retval = new StringDictionary();
if (File.Exists(configPath))
{
using (StreamReader sr = new StreamReader(configPath))
{
String line;
while ((line = sr.ReadLine()) != null)
{
line = line.Trim();
if (line.Length != 0)
{
retval.Add(line, null);
}
}
}
}
return retval;
}
}
public static class BlacklistMiddlewareExtensions
{
public static IApplicationBuilder UseBlacklist(this IApplicationBuilder builder)
{
return builder.UseMiddleware<BlacklistMiddleware>();
}
}
}

73
Teknik/Middleware/CORSMiddleware.cs

@ -1,73 +0,0 @@ @@ -1,73 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Teknik.Configuration;
using Teknik.Utilities;
using Teknik.Utilities.Routing;
namespace Teknik.Middleware
{
// You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
public class CORSMiddleware
{
private readonly RequestDelegate _next;
public CORSMiddleware(RequestDelegate next)
{
_next = next;
}
public Task InvokeAsync(HttpContext httpContext, Config config)
{
// Allow this domain, or everything if local
string origin = (httpContext.Request.IsLocal()) ? "*" : httpContext.Request.Headers["Origin"].ToString();
// Is the referrer set to the CDN and we are using a CDN?
if (config.UseCdn && !string.IsNullOrEmpty(config.CdnHost))
{
try
{
string host = httpContext.Request.Headers["Host"];
Uri uri = new Uri(config.CdnHost);
if (host == uri.Host)
origin = host;
}
catch { }
}
string domain = (string.IsNullOrEmpty(origin)) ? string.Empty : origin.GetDomain();
if (string.IsNullOrEmpty(origin))
{
string host = httpContext.Request.Headers["Host"];
string sub = host.GetSubdomain();
origin = (string.IsNullOrEmpty(sub)) ? config.Host : sub + "." + config.Host;
}
else
{
if (domain != config.Host)
{
string sub = origin.GetSubdomain();
origin = (string.IsNullOrEmpty(sub)) ? config.Host : sub + "." + config.Host;
}
}
httpContext.Response.Headers.Append("Access-Control-Allow-Origin", origin);
httpContext.Response.Headers.Append("Vary", "Origin");
return _next(httpContext);
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class CORSMiddlewareExtensions
{
public static IApplicationBuilder UseCORS(this IApplicationBuilder builder)
{
return builder.UseMiddleware<CORSMiddleware>();
}
}
}

93
Teknik/Middleware/ErrorHandlerMiddleware.cs

@ -1,93 +0,0 @@ @@ -1,93 +0,0 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Teknik.Areas.Error.Controllers;
using Teknik.Configuration;
using Teknik.Data;
using Teknik.Logging;
using Teknik.Utilities;
namespace Teknik.Middleware
{
public class ErrorHandlerMiddleware
{
private readonly RequestDelegate _next;
public ErrorHandlerMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext, ILogger<Logger> logger, Config config, TeknikEntities dbContext, ErrorController errorController)
{
var statusCodeFeature = new StatusCodePagesFeature();
httpContext.Features.Set<IStatusCodePagesFeature>(statusCodeFeature);
Exception exception = null;
try
{
await _next(httpContext);
}
catch (Exception ex)
{
exception = ex;
}
if (!statusCodeFeature.Enabled)
{
// Check if the feature is still available because other middleware (such as a web API written in MVC) could
// have disabled the feature to prevent HTML status code responses from showing up to an API client.
return;
}
// Do nothing if a response body has already been provided or not 404 response
if (httpContext.Response.HasStarted)
{
return;
}
// Detect if there is a response code or exception occured
if ((httpContext.Response.StatusCode >= 400 && httpContext.Response.StatusCode <= 600) || exception != null)
{
var routeData = httpContext.GetRouteData() ?? new RouteData();
var context = new ControllerContext();
context.HttpContext = httpContext;
context.RouteData = routeData;
context.ActionDescriptor = new Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor();
errorController.ControllerContext = context;
if (httpContext.Response.StatusCode == 500 || exception != null)
{
await errorController.Http500(exception).ExecuteResultAsync(context);
}
else
{
await errorController.HttpError(httpContext.Response.StatusCode).ExecuteResultAsync(context);
}
}
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class SetupErrorHandlerMiddlewareExtensions
{
public static IApplicationBuilder UseErrorHandler(this IApplicationBuilder builder, Config config)
{
return builder.UseMiddleware<ErrorHandlerMiddleware>();
}
}
}

68
Teknik/Middleware/PerformanceMonitorMiddleware.cs

@ -1,68 +0,0 @@ @@ -1,68 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Teknik.Configuration;
using Teknik.Utilities;
namespace Teknik.Middleware
{
// You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
public class PerformanceMonitorMiddleware
{
private readonly RequestDelegate _next;
public PerformanceMonitorMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext, Config config)
{
Stopwatch timer = new Stopwatch();
timer.Start();
httpContext.Response.OnStarting(state =>
{
var context = (HttpContext)state;
timer.Stop();
double ms = (double)timer.ElapsedMilliseconds;
string result = string.Format("{0:F0}", ms);
if (!httpContext.Response.Headers.IsReadOnly)
httpContext.Response.Headers.Add("GenerationTime", result);
return Task.CompletedTask;
}, httpContext);
await _next(httpContext);
// Don't interfere with non-HTML responses
if (httpContext.Response.ContentType != null && httpContext.Response.ContentType.StartsWith("text/html") && httpContext.Response.StatusCode == 200 && !httpContext.Request.IsAjaxRequest())
{
double ms = (double)timer.ElapsedMilliseconds;
string result = string.Format("{0:F0}", ms);
await httpContext.Response.WriteAsync(
"<script nonce=\"" + httpContext.Items[Constants.NONCE_KEY] + "\">" +
"var pageGenerationTime = '" + result + "';" +
"pageloadStopTimer();" +
"</script >");
}
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class PerformanceMonitorMiddlewareExtensions
{
public static IApplicationBuilder UsePerformanceMonitor(this IApplicationBuilder builder)
{
return builder.UseMiddleware<PerformanceMonitorMiddleware>();
}
}
}

53
Teknik/Middleware/SecurityHeadersMiddleware.cs

@ -1,53 +0,0 @@ @@ -1,53 +0,0 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Teknik.Configuration;
namespace Teknik.Middleware
{
public class SecurityHeadersMiddleware
{
private readonly RequestDelegate _next;
public SecurityHeadersMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext httpContext, Config config)
{
IHeaderDictionary headers = httpContext.Response.Headers;
// Access Control
headers.Append("Access-Control-Allow-Credentials", "true");
headers.Append("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS");
headers.Append("Access-Control-Allow-Headers", "Authorization, Accept, Origin, Content-Type, X-Requested-With, Connection, Transfer-Encoding");
// HSTS
headers.Append("strict-transport-security", "max-age=31536000; includeSubdomains; preload");
// XSS Protection
headers.Append("X-XSS-Protection", "1; mode=block");
// Content Type Options
headers.Append("X-Content-Type-Options", "nosniff");
// Referrer Policy
headers.Append("Referrer-Policy", "no-referrer, strict-origin-when-cross-origin");
return _next(httpContext);
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class SecurityHeadersMiddlewareExtensions
{
public static IApplicationBuilder UseSecurityHeaders(this IApplicationBuilder builder)
{
return builder.UseMiddleware<SecurityHeadersMiddleware>();
}
}
}

42
Teknik/Middleware/SetupHttpContextMiddleware.cs

@ -1,42 +0,0 @@ @@ -1,42 +0,0 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Teknik.Configuration;
using Teknik.Utilities;
namespace Teknik.Middleware
{
public class SetupHttpContextMiddleware
{
private readonly RequestDelegate _next;
public SetupHttpContextMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext, Config config)
{
// Setup the HTTP Context for everything else
// Generate the NONCE used for this request
string nonce = Convert.ToBase64String(Encoding.UTF8.GetBytes(StringHelper.RandomString(24)));
httpContext.Items[Constants.NONCE_KEY] = nonce;
await _next(httpContext);
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class SetupHttpContextMiddlewareExtensions
{
public static IApplicationBuilder UseHttpContextSetup(this IApplicationBuilder builder)
{
return builder.UseMiddleware<SetupHttpContextMiddleware>();
}
}
}

16
Teknik/Program.cs

@ -20,10 +20,10 @@ namespace Teknik @@ -20,10 +20,10 @@ namespace Teknik
{
AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior",
true);
BuildWebHost(args).Build().Run();
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder BuildWebHost(string[] args)
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(config =>
@ -42,18 +42,6 @@ namespace Teknik @@ -42,18 +42,6 @@ namespace Teknik
logging.AddProvider(new LoggerProvider(Config.Load(dataDir)));
logging.AddFilter<ConsoleLoggerProvider>("Microsoft.AspNetCore.Routing", LogLevel.Trace);
});
/*
.UseConfiguration(config)
.UseIISIntegration()
.UseStartup<Startup>()
.ConfigureLogging((hostingContext, logging) =>
{
string baseDir = hostingContext.HostingEnvironment.ContentRootPath;
string dataDir = Path.Combine(baseDir, "App_Data");
logging.AddProvider(new LoggerProvider(Config.Load(dataDir)));
});
*/
}
}
}

47
Teknik/Startup.cs

@ -26,6 +26,9 @@ using Microsoft.AspNetCore.Hosting; @@ -26,6 +26,9 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
using Teknik.Logging;
using Teknik.Utilities.Routing;
using Teknik.WebCommon.Middleware;
using Teknik.WebCommon;
using Teknik.Areas.Error.Controllers;
namespace Teknik
{
@ -63,8 +66,16 @@ namespace Teknik @@ -63,8 +66,16 @@ namespace Teknik
// Resolve the services from the service provider
var config = sp.GetService<Config>();
if (config.DevEnvironment)
var devEnv = config?.DevEnvironment ?? true;
var defaultConn = config?.DbConnection ?? string.Empty;
var authority = config?.UserConfig?.IdentityServerConfig?.Authority ?? string.Empty;
var host = config?.Host ?? string.Empty;
var apiName = config?.UserConfig?.IdentityServerConfig?.APIName ?? string.Empty;
var apiSecret = config?.UserConfig?.IdentityServerConfig?.APISecret ?? string.Empty;
var clientId = config?.UserConfig?.IdentityServerConfig?.ClientId ?? string.Empty;
var clientSecret = config?.UserConfig?.IdentityServerConfig?.ClientSecret ?? string.Empty;
if (devEnv)
{
Environment.EnvironmentName = Environments.Development;
}
@ -88,6 +99,7 @@ namespace Teknik @@ -88,6 +99,7 @@ namespace Teknik
services.AddHostedService<TrackingService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
services.AddScoped<IErrorController, ErrorController>();
// Add Tracking Filter scopes
//services.AddScoped<TrackDownload>();
@ -97,7 +109,7 @@ namespace Teknik @@ -97,7 +109,7 @@ namespace Teknik
// Create the Database Context
services.AddDbContext<TeknikEntities>(options => options
.UseLazyLoadingProxies()
.UseSqlServer(config.DbConnection), ServiceLifetime.Transient);
.UseSqlServer(defaultConn), ServiceLifetime.Transient);
// Cookie Policies
services.Configure<CookiePolicyOptions>(options =>
@ -109,7 +121,7 @@ namespace Teknik @@ -109,7 +121,7 @@ namespace Teknik
services.ConfigureApplicationCookie(options =>
{
options.Cookie.Domain = CookieHelper.GenerateCookieDomain(config.Host, false, Environment.IsDevelopment());
options.Cookie.Domain = CookieHelper.GenerateCookieDomain(host, false, Environment.IsDevelopment());
options.Cookie.Name = "TeknikWeb";
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict;
@ -136,7 +148,7 @@ namespace Teknik @@ -136,7 +148,7 @@ namespace Teknik
// Set the anti-forgery cookie name
services.AddAntiforgery(options =>
{
options.Cookie.Domain = CookieHelper.GenerateCookieDomain(config.Host, false, Environment.IsDevelopment());
options.Cookie.Domain = CookieHelper.GenerateCookieDomain(host, false, Environment.IsDevelopment());
options.Cookie.Name = "TeknikWebAntiForgery";
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict;
@ -151,11 +163,11 @@ namespace Teknik @@ -151,11 +163,11 @@ namespace Teknik
})
.AddIdentityServerAuthentication(options =>
{
options.Authority = config.UserConfig.IdentityServerConfig.Authority;
options.Authority = authority;
options.RequireHttpsMetadata = true;
options.ApiName = config.UserConfig.IdentityServerConfig.APIName;
options.ApiSecret = config.UserConfig.IdentityServerConfig.APISecret;
options.ApiName = apiName;
options.ApiSecret = apiSecret;
options.NameClaimType = "username";
options.RoleClaimType = JwtClaimTypes.Role;
@ -166,7 +178,7 @@ namespace Teknik @@ -166,7 +178,7 @@ namespace Teknik
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict;
options.ExpireTimeSpan = TimeSpan.FromDays(30);
options.Cookie.Name = "TeknikWebAuth";
options.Cookie.Domain = CookieHelper.GenerateCookieDomain(config.Host, false, Environment.IsDevelopment());
options.Cookie.Domain = CookieHelper.GenerateCookieDomain(host, false, Environment.IsDevelopment());
options.EventsType = typeof(CookieEventHandler);
})
@ -174,11 +186,11 @@ namespace Teknik @@ -174,11 +186,11 @@ namespace Teknik
{
options.SignInScheme = "Cookies";
options.Authority = config.UserConfig.IdentityServerConfig.Authority;
options.Authority = authority;
options.RequireHttpsMetadata = true;
options.ClientId = config.UserConfig.IdentityServerConfig.ClientId;
options.ClientSecret = config.UserConfig.IdentityServerConfig.ClientSecret;
options.ClientId = clientId;
options.ClientSecret = clientSecret;
options.ResponseType = "code id_token";
// Set the scopes to listen to
@ -255,8 +267,11 @@ namespace Teknik @@ -255,8 +267,11 @@ namespace Teknik
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, TeknikEntities dbContext, Config config)
{
var host = config?.Host ?? string.Empty;
var shortHost = config?.ShortenerConfig?.ShortenerHost ?? string.Empty;
// Create and Migrate the database
dbContext.Database.Migrate();
dbContext?.Database?.Migrate();
// Setup static files and cache them client side
app.UseStaticFiles(new StaticFileOptions
@ -279,7 +294,7 @@ namespace Teknik @@ -279,7 +294,7 @@ namespace Teknik
IdleTimeout = TimeSpan.FromMinutes(30),
Cookie = new CookieBuilder()
{
Domain = CookieHelper.GenerateCookieDomain(config.Host, false, Environment.IsDevelopment()),
Domain = CookieHelper.GenerateCookieDomain(host, false, Environment.IsDevelopment()),
Name = "TeknikWebSession",
SecurePolicy = CookieSecurePolicy.SameAsRequest,
SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict
@ -296,7 +311,7 @@ namespace Teknik @@ -296,7 +311,7 @@ namespace Teknik
app.UseHttpsRedirection();
// Use Exception Handling
app.UseErrorHandler(config);
app.UseErrorHandler();
// Performance Monitor the entire request
app.UsePerformanceMonitor();
@ -317,7 +332,7 @@ namespace Teknik @@ -317,7 +332,7 @@ namespace Teknik
// And finally, let's use MVC
app.UseEndpoints(endpoints =>
{
endpoints.BuildEndpoints(config.Host, config.ShortenerConfig?.ShortenerHost);
endpoints.BuildEndpoints(host, shortHost);
});
}

5
Teknik/Teknik.csproj

@ -97,6 +97,7 @@ @@ -97,6 +97,7 @@
<ProjectReference Include="..\MailService\MailService.csproj" />
<ProjectReference Include="..\Tracking\Tracking.csproj" />
<ProjectReference Include="..\Utilities\Utilities.csproj" />
<ProjectReference Include="..\WebCommon\WebCommon.csproj" />
</ItemGroup>
<ItemGroup>
@ -135,8 +136,4 @@ @@ -135,8 +136,4 @@
<ProjectExtensions><VisualStudio><UserProperties /></VisualStudio></ProjectExtensions>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="npm run build" />
</Target>
</Project>

4
Teknik/package.json

@ -41,7 +41,7 @@ @@ -41,7 +41,7 @@
"plugin-error": "^1.0.1",
"pump": "^3.0.0",
"rimraf": "^3.0.2",
"uglify-es": "^3.3.9"
"uglify-es": "^3.3.10"
},
"keywords": [
"Teknik",
@ -54,7 +54,7 @@ @@ -54,7 +54,7 @@
"name": "teknik",
"repository": {
"type": "git",
"url": "https://git.teknik.io/Teknikode/TeknikCore"
"url": "https://git.teknik.io/Teknikode/Teknik"
},
"scripts": {
"pre-publish": "npm install && gulp clean && gulp copy-assets && gulp watch",

Loading…
Cancel
Save