예제 #1
0
        public void CanWeAuthorizeNonMasterKey()
        {
            var result = B2Client.Authorize(applicationKeyId, applicationKey);

            Console.WriteLine(JsonConvert.SerializeObject(result));
            Assert.IsFalse(string.IsNullOrEmpty(result.AuthorizationToken));
        }
예제 #2
0
        public void UpdateBucketTest()
        {
            var name   = "B2NETUpdateBucket";
            var client = new B2Client(Options);

            Options = client.Authorize().Result;

            //Creat a bucket to delete
            var bucket = client.Buckets.Create(name, BucketTypes.allPrivate).Result;

            try {
                if (!string.IsNullOrEmpty(bucket.BucketId))
                {
                    var updatedBucket = client.Buckets.Update(BucketTypes.allPublic, bucket.BucketId).Result;
                    Assert.AreEqual(BucketTypes.allPublic.ToString(), updatedBucket.BucketType);
                }
                else
                {
                    Assert.Fail("The bucket was not deleted. The response did not contain a bucketid.");
                }
            } catch (Exception ex) {
                Assert.Fail(ex.Message);
            } finally {
                client.Buckets.Delete(bucket.BucketId).Wait();
            }
        }
예제 #3
0
        public void CanWeAuthorizeStatic()
        {
            var result = B2Client.Authorize(Options);

            Console.WriteLine(JsonConvert.SerializeObject(result));
            Assert.IsFalse(string.IsNullOrEmpty(result.AuthorizationToken));
        }
예제 #4
0
        public void CreateBucketWithCacheControlTest()
        {
            var name   = "B2NETTestingBucket";
            var client = new B2Client(Options);

            Options = client.Authorize().Result;

            var bucket = client.Buckets.Create(name, new B2BucketOptions()
            {
                CacheControl = 600
            }).Result;

            // Get bucket to check for info
            var bucketList = client.Buckets.GetList().Result;

            // Clean up
            if (!string.IsNullOrEmpty(bucket.BucketId))
            {
                client.Buckets.Delete(bucket.BucketId).Wait();
            }

            var savedBucket = bucketList.FirstOrDefault(b => b.BucketName == bucket.BucketName);

            Assert.IsNotNull(savedBucket, "Retreived bucket was null");
            Assert.IsNotNull(savedBucket.BucketInfo, "Bucekt info was null");
            Assert.IsTrue(savedBucket.BucketInfo.ContainsKey("cache-control"), "Bucket info did not contain Cache-Control");
            Assert.AreEqual("max-age=600", savedBucket.BucketInfo["cache-control"], "Cache-Control values were not equal.");
        }
예제 #5
0
        public void setParams(string connstr)
        {
            var parts = BUCommon.FileSvcBase.ParseConnStr(connstr);
            var opts  = new B2Net.Models.B2Options()
            {
                KeyId            = parts[0].Trim()
                , ApplicationKey = parts[1].Trim()
            };

            //opts.AuthorizationToken = account.auth["AuthorizationToken"];
            //opts.DownloadUrl = account.auth["DownloadUrl"];
            //opts.ApiUrl = account.auth["ApiUrl"];

            /*
             * opts.AuthorizationToken = "<token>";
             * opts.DownloadUrl = "https://f001.backblazeb2.com";
             * opts.ApiUrl = "https://api001.backblazeb2.com";
             */
            _opts = opts;
            account.auth["AuthorizationToken"] = opts.AuthorizationToken;
            account.auth["DownloadUrl"]        = opts.DownloadUrl;
            account.auth["ApiUrl"]             = opts.ApiUrl;

            _client = new B2Client(opts);

            /*
             * var blst = x.Buckets.GetList().Result;
             *
             * var bkt = blst.FirstOrDefault();
             * var flst = x.Files.GetList(bkt.BucketId);
             */
        }
예제 #6
0
        public void Initialize()
        {
            Client     = new B2Client(Options.AccountId, Options.ApplicationKey);
            BucketName = $"B2NETTestingBucket-{Path.GetRandomFileName().Replace(".", "").Substring(0, 6)}";

            var      buckets        = Client.Buckets.GetList().Result;
            B2Bucket existingBucket = null;

            foreach (B2Bucket b2Bucket in buckets)
            {
                if (b2Bucket.BucketName == BucketName)
                {
                    existingBucket = b2Bucket;
                }
            }

            if (existingBucket != null)
            {
                TestBucket = existingBucket;
            }
            else
            {
                TestBucket = Client.Buckets.Create(BucketName, BucketTypes.allPrivate).Result;
            }
        }
예제 #7
0
        public void Initialize()
        {
            Client  = new B2Client(Options);
            Options = Client.Authorize().Result;

            var      buckets        = Client.Buckets.GetList().Result;
            B2Bucket existingBucket = null;

            foreach (B2Bucket b2Bucket in buckets)
            {
                if (b2Bucket.BucketName == "B2NETTestingBucket")
                {
                    existingBucket = b2Bucket;
                }
            }

            if (existingBucket != null)
            {
                TestBucket = existingBucket;
            }
            else
            {
                TestBucket = Client.Buckets.Create("B2NETTestingBucket", BucketTypes.allPrivate).Result;
            }
        }
예제 #8
0
        public B2StorageProvider(IConfiguration config, ILogger <B2StorageProvider> logger)
        {
            _b2Options = new B2Options()
            {
                AccountId      = config["Auth:B2:AccountId"],
                KeyId          = config["Auth:B2:KeyId"],
                ApplicationKey = config["Auth:B2:AppKey"],
                BucketId       = config["Auth:B2:BucketId"],
                PersistBucket  = true
            };

            // if backblaze isn't fully configured
            if (string.IsNullOrEmpty(_b2Options.AccountId) ||
                string.IsNullOrEmpty(_b2Options.KeyId) ||
                string.IsNullOrEmpty(_b2Options.ApplicationKey) ||
                string.IsNullOrEmpty(_b2Options.BucketId))
            {
                throw new InvalidOperationException("Backblaze not fully configured.");
            }

            _client = new B2Client(B2Client.Authorize(_b2Options));
            _logger = logger;

            _bucketName = _client.Buckets.GetList().Result
                          .Single(b => b.BucketId == _b2Options.BucketId)
                          .BucketName;
        }
예제 #9
0
 /// <summary>
 /// Creates a pre-authorized download URL for the database with <paramref name="dbName" /> that is valid for <paramref name="duration" />.
 /// </summary>
 /// <param name="client">The <see cref="B2Client" /> created by <see cref="GetClient" /> with access to a bucket (hopefully containing the DB).</param>
 /// <param name="dbName">The filename (with extension) of the database on the B2 bucket.</param>
 /// <param name="duration">The duration (in seconds) for the link to be valid for. Defaults to 86400s (1 day), minimum of 1s, and maximum of 604800s (1 week).</param>
 /// <returns>A pre-authorized download URL for the database.</returns>
 public static async Task <string> GetDownloadUrlWithAuth(B2Client client, string dbName, int duration = 86400)
 {
     return(client == null
                         ? null
                         : GetFriendlyUrl(client, dbName) + "?Authorization=" +
            (await client.Files.GetDownloadAuthorization(dbName, duration, client.Capabilities.BucketId))
            .AuthorizationToken);
 }
예제 #10
0
        public void CanWeAuthorize()
        {
            var client = new B2Client(Options);

            var result = client.Authorize().Result;

            Assert.IsFalse(string.IsNullOrEmpty(result.AuthorizationToken));
        }
예제 #11
0
 public async Task BadInitialization()
 {
     // Missing AccountId
     var auth = await B2Client.AuthorizeAsync(new B2Options()
     {
         KeyId          = applicationKeyId,
         ApplicationKey = ""
     });
 }
예제 #12
0
        public B2UploadStream(B2Client client, string fileName, string bucketId, Dictionary <string, string> fileInfo)
        {
            _client   = client;
            _fileName = fileName;
            _bucketId = bucketId;
            _fileInfo = fileInfo;

            _buffer = new MemoryStream(MinPartSize);
        }
예제 #13
0
        public void DoWeGetCapabilitiesOnApplicationKey()
        {
            var result = B2Client.Authorize(applicationKeyId, applicationKey);

            Assert.IsFalse(string.IsNullOrEmpty(result.AuthorizationToken));

            Assert.IsNotNull(result.Capabilities);
            Assert.IsNotNull(result.Capabilities.Capabilities);
        }
예제 #14
0
 public void BadInitialization()
 {
     // Missing AccountId
     var client = new B2Client(B2Client.Authorize(new B2Options()
     {
         KeyId          = applicationKeyId,
         ApplicationKey = applicationKey
     }));
 }
예제 #15
0
 public void BadInitialization()
 {
     // Missing AccountId
     var client = new B2Client(B2Client.Authorize(new B2Options()
     {
         KeyId          = "00151189a8b4c7a0000000006",
         ApplicationKey = "K001+GGkBNcbJVj3LD4+e3s5pCUMQ7U"
     }));
 }
예제 #16
0
        public void DoWeGetOptionsBack()
        {
            var result = B2Client.Authorize(Options);

            Assert.AreNotEqual("0", result.AbsoluteMinimumPartSize);
            Assert.AreNotEqual("0", result.MinimumPartSize);
            Assert.AreNotEqual("0", result.RecommendedPartSize);
            Assert.IsFalse(string.IsNullOrEmpty(result.DownloadUrl));
            Assert.IsFalse(string.IsNullOrEmpty(result.ApiUrl));
        }
예제 #17
0
        public async Task GetBucketListTest()
        {
            // Key that is restricted to a specific bucket name above.
            var client = new B2Client(B2Client.Authorize(new B2Options()
            {
                KeyId          = restrictedApplicationKeyId,
                ApplicationKey = restrictedApplicationKey
            }));

            BucketName = $"B2NETTestingBucket-{Path.GetRandomFileName().Replace(".", "").Substring(0, 6)}";

            var bucket = await client.Buckets.Create(BucketName, BucketTypes.allPrivate);
        }
예제 #18
0
        public void UpdateBucketWithLifecycleRulesTest()
        {
            var name   = "B2NETTestingBucket";
            var client = new B2Client(Options);

            Options = client.Authorize().Result;

            var bucket = client.Buckets.Create(name, new B2BucketOptions()
            {
                LifecycleRules = new System.Collections.Generic.List <B2BucketLifecycleRule>()
                {
                    new B2BucketLifecycleRule()
                    {
                        DaysFromHidingToDeleting  = 30,
                        DaysFromUploadingToHiding = 15,
                        FileNamePrefix            = "testing"
                    }
                }
            }).Result;

            // Update bucket with new info
            bucket = client.Buckets.Update(new B2BucketOptions()
            {
                LifecycleRules = new System.Collections.Generic.List <B2BucketLifecycleRule>()
                {
                    new B2BucketLifecycleRule()
                    {
                        DaysFromHidingToDeleting  = 10,
                        DaysFromUploadingToHiding = 10,
                        FileNamePrefix            = "tested"
                    }
                }
            }, bucket.BucketId).Result;

            // Get bucket to check for info
            var bucketList = client.Buckets.GetList().Result;

            // Clean up
            if (!string.IsNullOrEmpty(bucket.BucketId))
            {
                client.Buckets.Delete(bucket.BucketId).Wait();
            }

            var savedBucket = bucketList.FirstOrDefault(b => b.BucketName == bucket.BucketName);

            Assert.IsNotNull(savedBucket, "Retreived bucket was null");
            Assert.IsNotNull(savedBucket.BucketInfo, "Bucekt info was null");
            Assert.AreEqual(savedBucket.LifecycleRules.Count, 1, "Lifecycle rules count was " + savedBucket.LifecycleRules.Count);
            Assert.AreEqual("tested", savedBucket.LifecycleRules.First().FileNamePrefix, "File name prefixes in the first lifecycle rule were not equal.");
        }
예제 #19
0
        public async Task GetBucketListTest()
        {
            // Key that is restricted to RestrictedBucketName above.
            var client = new B2Client(B2Client.Authorize(new B2Options()
            {
                AccountId      = TestConstants.AccountId,
                KeyId          = "00151189a8b4c7a0000000006",
                ApplicationKey = "K001+GGkBNcbJVj3LD4+e3s5pCUMQ7U"
            }));

            BucketName = $"B2NETTestingBucket-{Path.GetRandomFileName().Replace(".", "").Substring(0, 6)}";

            var bucket = await client.Buckets.Create(BucketName, BucketTypes.allPrivate);
        }
예제 #20
0
        public void GetBucketListTest()
        {
            var client = new B2Client(Options);

            Options = client.Authorize().Result;

            var bucket = client.Buckets.Create("B2NETTestingBucket", BucketTypes.allPrivate).Result;

            var list = client.Buckets.GetList().Result;

            var deletedBucket = client.Buckets.Delete(bucket.BucketId).Result;

            Assert.AreNotEqual(0, list.Count);
        }
예제 #21
0
        /// <summary>
        /// Uploads the locally-stored database <paramref name="localDb" /> to the bucket that <paramref name="client" /> has access to.
        /// </summary>
        /// <param name="client">The <see cref="B2Client" /> created by <see cref="GetClient" /> with access to a bucket to upload to.</param>
        /// <param name="localDb">The local database to upload.</param>
        /// <returns><see langword="true" /> if the upload was successful, or <see langword="false" /> otherwise.</returns>
        public static async Task <bool> UploadDbAsync(B2Client client, PwDatabase localDb)
        {
            if (client == null)
            {
                return(false);
            }

            Interface.UpdateStatus("Uploading database...");

            string localPath = localDb.IOConnectionInfo.Path;

            byte[] fileData;
            using (FileStream fs = File.OpenRead(localPath))
            {
                if (!fs.CanRead)
                {
                    return(false);
                }

                using (MemoryStream ms = new MemoryStream())
                {
                    fs.CopyTo(ms);
                    fileData = ms.ToArray();
                }
            }

            try
            {
                B2UploadUrl uploadUrl = await client.Files.GetUploadUrl(client.Capabilities.BucketId);

                B2File file = await client.Files.Upload(fileData, Path.GetFileName(localPath), uploadUrl, true,
                                                        client.Capabilities.BucketId);
            }
            catch (Exception e)
            {
                if (new [] { typeof(SocketException), typeof(WebException), typeof(HttpRequestException), typeof(AggregateException), typeof(InvalidOperationException) }.Contains(e.GetType()))
                {
                    Interface.UpdateStatus("Unable to upload the database to B2.");
                    return(false);
                }

                throw;
            }

            Interface.UpdateStatus("Database upload successful.");

            return(true);
        }
예제 #22
0
        private static B2Client GetClient()
        {
            B2Client client;

            //Attempt to establish a connection using the credentials provided
            try
            {
                client = new B2Client(_config.KeyId, _config.ApplicationKey);
            }
            catch (Exception e)
            {
                if (e.GetType() == typeof(AuthorizationException))
                {
                    Interface.UpdateStatus("Unable to authenticate with Backblaze B2 servers. Please make sure the keys you are using are valid.");
                    return(null);
                }

                if (new[] { typeof(SocketException), typeof(WebException), typeof(HttpRequestException), typeof(AggregateException), typeof(InvalidOperationException) }.Contains(e.GetType()))
                {
                    Interface.UpdateStatus("Unable to reach Backblaze B2 servers. Check your internet connection.");
                    return(null);
                }

                throw;
            }

            //Verify that the credentials being used are specific to a single bucket, and that we know what that bucket is
            if (string.IsNullOrWhiteSpace(client.Capabilities.BucketName) ||
                string.IsNullOrWhiteSpace(client.Capabilities.BucketId))
            {
                Interface.UpdateStatus(
                    "The key used is not specific to a single bucket. Please create a new key that is restricted to the bucket where you would like to store the database.");
                return(null);
            }

            //Verify that the credentials have sufficient permissions for the required operations
            if (!RequiredPerms.IsSubsetOf(client.Capabilities.Capabilities))
            {
                Interface.UpdateStatus("The key used does not have the necessary permissions. It is missing the following: " +
                                       string.Join(", ", RequiredPerms.Except(client.Capabilities.Capabilities)));
                return(null);
            }

            Interface.UpdateStatus("Connected to B2 successfully.");

            return(client);
        }
예제 #23
0
        /// <summary>
        /// connect to b2.
        /// </summary>
        public void authorize()
        {
            var opts = new B2Net.Models.B2Options
            {
                KeyId            = _opts.AccountId
                , ApplicationKey = _opts.ApplicationKey
            };

            _client = new B2Client(opts);

            _opts = _client.Authorize().Result;
            account.auth["AuthorizationToken"]  = _opts.AuthorizationToken;
            account.auth["DownloadUrl"]         = _opts.DownloadUrl;
            account.auth["ApiUrl"]              = _opts.ApiUrl;
            account.auth["MinPartSize"]         = string.Format("{0:n}", _opts.AbsoluteMinimumPartSize);
            account.auth["RecommendedPartSize"] = string.Format("{0:n}", _opts.RecommendedPartSize);
        }
예제 #24
0
        public void CreateBucketTest()
        {
            var name   = "B2NETTestingBucket";
            var client = new B2Client(Options);

            Options = client.Authorize().Result;

            var bucket = client.Buckets.Create(name, BucketTypes.allPrivate).Result;

            // Clean up
            if (!string.IsNullOrEmpty(bucket.BucketId))
            {
                client.Buckets.Delete(bucket.BucketId).Wait();
            }

            Assert.AreEqual(name, bucket.BucketName);
        }
예제 #25
0
        /// <summary>
        /// Downloads the database with <paramref name="dbName" /> from the bucket <paramref name="client" /> has access to, and stores it in a directory in the temporary location of the environment.
        /// </summary>
        /// <param name="client">The <see cref="B2Client" /> created by <see cref="GetClient" /> with access to a bucket (hopefully containing the DB).</param>
        /// <param name="dbName">The filename (with extension) of the database to download from the B2 bucket.</param>
        /// <returns>The filepath where the database was downloaded to temporarily, or <see langword="null" /> if the download failed.</returns>
        public static async Task <string> DownloadDbAsync(B2Client client, string dbName)
        {
            if (client == null)
            {
                return(null);
            }

            Interface.UpdateStatus("Downloading database...");

            //Download to memory
            B2File file;

            try
            {
                file = await client.Files.DownloadByName(dbName, client.Capabilities.BucketName);
            }
            catch (B2Exception)
            {
                Interface.UpdateStatus("The database does not exist on B2 to download.");
                return(null);
            }

            //Write the file to a temporary location
            string tempDir  = Path.Combine(Path.GetTempPath(), "KeePass", "B2Sync");
            string tempPath = Path.Combine(tempDir, file.FileName);

            if (!Directory.Exists(tempDir))
            {
                Directory.CreateDirectory(tempDir);
            }
            using (MemoryStream ms = new MemoryStream(file.FileData))
            {
                using (FileStream fs = File.OpenWrite(tempPath))
                {
                    ms.CopyTo(fs);
                    fs.Flush(true);
                }
            }

            Interface.UpdateStatus("Database download successful.");

            return(tempPath);
        }
예제 #26
0
파일: B2Lib.cs 프로젝트: ijat/b2clone
 public B2Lib(string keyId, string appId, string bucketId)
 {
     try
     {
         B2Db      = new B2Db();
         B2Options = new B2Options()
         {
             BucketId       = bucketId,
             PersistBucket  = true,
             KeyId          = keyId,
             ApplicationKey = appId,
         };
         Client = new B2Client(B2Options);
         B2Map  = new ConcurrentDictionary <string, B2File>();
     }
     catch (Exception e)
     {
         Log.Error(e.ToString());
     }
 }
예제 #27
0
        public void DeleteBucketTest()
        {
            var name   = "B2NETDeleteBucket";
            var client = new B2Client(Options);

            Options = client.Authorize().Result;

            //Creat a bucket to delete
            var bucket = client.Buckets.Create(name, BucketTypes.allPrivate).Result;

            if (!string.IsNullOrEmpty(bucket.BucketId))
            {
                var deletedBucket = client.Buckets.Delete(bucket.BucketId).Result;
                Assert.AreEqual(name, deletedBucket.BucketName);
            }
            else
            {
                Assert.Fail("The bucket was not deleted. The response did not contain a bucketid.");
            }
        }
예제 #28
0
        public B2WorkshopUploader(JObject uploadParams)
        {
            HttpClientFactory.SetHttpClient(HttpClientHolder.Get());

            var config = uploadParams.ToString();

            if (_b2ClientConfig == config)
            {
                _b2Client = _b2ClientLast;
            }
            else
            {
                _b2Client = new B2Client(new B2Options {
                    KeyId          = uploadParams["keyID"].ToString(),
                    ApplicationKey = uploadParams["keyValue"].ToString(),
                    BucketId       = uploadParams["bucketID"].ToString(),
                    PersistBucket  = true
                });
                _b2ClientLast   = _b2Client;
                _b2ClientConfig = config;
            }
            _b2ClientPrefix = uploadParams["prefix"].ToString();
        }
예제 #29
0
        public void CanWeAuthorizeStatic()
        {
            var result = B2Client.Authorize(Options);

            Assert.IsFalse(string.IsNullOrEmpty(result.AuthorizationToken));
        }
예제 #30
0
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext <ApplicationDbContext>(options => options.UseNpgsql(
                                                             Environment.GetEnvironmentVariable("DATABASE_URL") ?? Configuration.GetConnectionString("DbConnection")
                                                             ));
            services.AddDatabaseDeveloperPageExceptionFilter();

            // Repositories
            services.AddScoped <UserRepository>();
            services.AddScoped <ClubRepository>();
            services.AddScoped <ThreadRepository>();
            services.AddScoped <StoriesRepository>();
            services.AddScoped <TagsRepository>();
            services.AddScoped <ChaptersRepository>();
            services.AddScoped <BlogpostsRepository>();
            services.AddScoped <CommentsRepository>();
            services.AddScoped <FoldersRepository>();
            services.AddScoped <NotificationsRepository>();

            // Custom persistent config
            services.AddSingleton(OgmaConfig.Init("config.json"));

            // Routing
            services.AddRouting(options => options.LowercaseUrls = true);

            // HttpContextAccessor
            services.AddHttpContextAccessor();

            // ActionContextAccessor
            services.AddSingleton <IActionContextAccessor, ActionContextAccessor>();

            // UrlHelperFactory
            services.AddSingleton <IUrlHelperFactory, UrlHelperFactory>();

            // Identity
            services.AddIdentity <OgmaUser, OgmaRole>(config =>
            {
                config.SignIn.RequireConfirmedEmail   = true;
                config.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_ ";
                config.User.RequireUniqueEmail        = true;
            })
            .AddEntityFrameworkStores <ApplicationDbContext>()
            .AddUserManager <OgmaUserManager>()
            .AddDefaultTokenProviders()
            .AddUserStore <UserStore <OgmaUser, OgmaRole, ApplicationDbContext, long, IdentityUserClaim <long>, UserRole, IdentityUserLogin <long>, IdentityUserToken <long>, IdentityRoleClaim <long> > >()
            .AddRoleStore <RoleStore <OgmaRole, ApplicationDbContext, long, UserRole, IdentityRoleClaim <long> > >();

            // Logged in user service
            services.AddTransient <IUserService, UserService>();

            // Claims
            services.AddScoped <IUserClaimsPrincipalFactory <OgmaUser>, OgmaClaimsPrincipalFactory>();
            services.AddScoped(s => s.GetService <IHttpContextAccessor>()?.HttpContext?.User);

            // Argon2 hasher
            services.UpgradePasswordSecurity().UseArgon2 <OgmaUser>();

            // Email
            services.AddTransient <IEmailSender, EmailSender>();
            services.Configure <AuthMessageSenderOptions>(Configuration);

            // Backblaze
            var b2Options = Configuration.GetSection("B2").Get <B2Options>();
            var b2Client  = new B2Client(B2Client.Authorize(b2Options));

            services.AddSingleton <IB2Client>(b2Client);

            // File uploader
            services.AddSingleton <ImageUploader>();

            // ReCaptcha
            services.Configure <RecaptchaSettings>(Configuration.GetSection("RecaptchaSettings"));
            services.AddTransient <IRecaptchaService, RecaptchaService>();

            // Seeding
            services.AddAsyncInitializer <DbSeedInitializer>();

            // Auth
            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);

            // Auth
            services.AddAuthorization(options =>
            {
                options.AddPolicy("RequireAdminRole", policy => policy.RequireRole("Admin"));
            });


            // Cookies
            services.ConfigureApplicationCookie(options =>
            {
                options.LoginPath           = new PathString("/login");
                options.LogoutPath          = new PathString("/logout");
                options.AccessDeniedPath    = new PathString("/login");
                options.Cookie.SameSite     = SameSiteMode.Lax;
                options.Cookie.SecurePolicy = CookieSecurePolicy.Always;

                options.Events.OnRedirectToLogin =
                    HandleApiRequest(StatusCodes.Status401Unauthorized, options.Events.OnRedirectToLogin);
                options.Events.OnRedirectToAccessDenied =
                    HandleApiRequest(StatusCodes.Status403Forbidden, options.Events.OnRedirectToLogin);
            });

            // Compression
            services.AddResponseCompression();

            // Cache
            services.AddMemoryCache();

            // Automapper
            services.AddAutoMapper(typeof(AutoMapperProfile));

            // Runtime compilation
            services.AddControllersWithViews().AddRazorRuntimeCompilation();

            // Razor
            services.AddRazorPages().AddRazorPagesOptions(options =>
            {
                options.Conventions.AuthorizeAreaFolder("Admin", "/", "RequireAdminRole");
                options.Conventions
                .AddPageRoute("/Club/Index", "/club/{slug}-{id}")
                .AddAreaPageRoute("Identity", "/Account/Manage/ChangePassword", "/.well-known/change-password");
            });

            // MVC
            services
            .AddMvc(options =>
            {
                options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
            })
            .AddJsonOptions(options =>
            {
                options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
                options.JsonSerializerOptions.IgnoreNullValues = true;
            });

            // SignalR
            services.AddSignalR();
        }