User registration now actually works

Also package updates
This commit is contained in:
Thomas Hounsell 2017-04-12 15:04:23 +01:00
parent 74cbe3cb22
commit 80642a2402
39 changed files with 1600 additions and 954 deletions

View File

@ -0,0 +1,133 @@
using System;
namespace MongoAuth
{
/* Based off http://stackoverflow.com/a/7135008 */
internal static class Base32Encoding
{
public static byte[] ToBytes(string input)
{
if (string.IsNullOrEmpty(input))
{
throw new ArgumentNullException(nameof(input));
}
input = input.TrimEnd('='); //remove padding characters
int byteCount = input.Length * 5 / 8; //this must be TRUNCATED
var returnArray = new byte[byteCount];
byte curByte = 0,
bitsRemaining = 8;
int arrayIndex = 0;
foreach (char c in input)
{
int cValue = CharToValue(c);
int mask;
if (bitsRemaining > 5)
{
mask = cValue << (bitsRemaining - 5);
curByte = (byte)(curByte | mask);
bitsRemaining -= 5;
}
else
{
mask = cValue >> (5 - bitsRemaining);
curByte = (byte)(curByte | mask);
returnArray[arrayIndex++] = curByte;
curByte = (byte)(cValue << (3 + bitsRemaining));
bitsRemaining += 3;
}
}
//if we didn't end with a full byte
if (arrayIndex != byteCount)
{
returnArray[arrayIndex] = curByte;
}
return returnArray;
}
public static string ToString(byte[] input)
{
if (input == null || input.Length == 0)
{
throw new ArgumentNullException(nameof(input));
}
int charCount = (int)Math.Ceiling(input.Length / 5d) * 8;
var returnArray = new char[charCount];
byte nextChar = 0,
bitsRemaining = 5;
int arrayIndex = 0;
foreach (byte b in input)
{
nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining)));
returnArray[arrayIndex++] = ValueToChar(nextChar);
if (bitsRemaining < 4)
{
nextChar = (byte)((b >> (3 - bitsRemaining)) & 31);
returnArray[arrayIndex++] = ValueToChar(nextChar);
bitsRemaining += 5;
}
bitsRemaining -= 3;
nextChar = (byte)((b << bitsRemaining) & 31);
}
//if we didn't end with a full char
if (arrayIndex != charCount)
{
returnArray[arrayIndex++] = ValueToChar(nextChar);
while (arrayIndex != charCount)
returnArray[arrayIndex++] = '='; //padding
}
return new string(returnArray);
}
private static int CharToValue(char c)
{
int value = c;
//65-90 == uppercase letters
if (value < 91 && value > 64)
{
return value - 65;
}
//50-55 == numbers 2-7
if (value < 56 && value > 49)
{
return value - 24;
}
//97-122 == lowercase letters
if (value < 123 && value > 96)
{
return value - 97;
}
throw new ArgumentException("Character is not a Base32 character.", nameof(c));
}
private static char ValueToChar(byte b)
{
if (b < 26)
{
return (char)(b + 65);
}
if (b < 32)
{
return (char)(b + 24);
}
throw new ArgumentException("Byte is not a value Base32 value.", nameof(b));
}
}
}

View File

@ -2,34 +2,34 @@
namespace MongoAuth
{
internal static class DatabaseConfig
{
public static string Host { get; }
public static int Port { get; }
public static string Database { get; }
public static string Username { get; }
public static string Password { get; }
internal static class DatabaseConfig
{
public static string Host { get; }
public static int Port { get; }
public static string Database { get; }
public static string Username { get; }
public static string Password { get; }
static DatabaseConfig()
{
Host = !string.IsNullOrEmpty(ConfigurationManager.AppSettings["data:MongoHost"])
? ConfigurationManager.AppSettings["data:MongoHost"]
: "localhost";
static DatabaseConfig()
{
Host = !string.IsNullOrEmpty(ConfigurationManager.AppSettings["data:MongoHost"])
? ConfigurationManager.AppSettings["data:MongoHost"]
: "localhost";
int _port;
bool success = int.TryParse(ConfigurationManager.AppSettings["data:MongoPort"], out _port);
if (!success)
{
_port = 27017; // mongo default port
}
Port = _port;
int port;
bool success = int.TryParse(ConfigurationManager.AppSettings["data:MongoPort"], out port);
if (!success)
{
port = 27017; // mongo default port
}
Port = port;
Database = !string.IsNullOrEmpty(ConfigurationManager.AppSettings["data:MongoDB"])
? ConfigurationManager.AppSettings["data:MongoDB"]
: "MongoAuth";
Database = !string.IsNullOrEmpty(ConfigurationManager.AppSettings["data:MongoDB"])
? ConfigurationManager.AppSettings["data:MongoDB"]
: "MongoAuth";
Username = ConfigurationManager.AppSettings["data:MongoUser"] ?? "";
Password = ConfigurationManager.AppSettings["data:MongoPass"] ?? "";
}
}
Username = ConfigurationManager.AppSettings["data:MongoUser"] ?? "";
Password = ConfigurationManager.AppSettings["data:MongoPass"] ?? "";
}
}
}

View File

@ -56,6 +56,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Base32Encoding.cs" />
<Compile Include="DatabaseConfig.cs" />
<Compile Include="MongoMembershipProvider.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
@ -12,419 +13,468 @@
namespace MongoAuth
{
public class MongoMembershipProvider : MembershipProvider
{
private const string MEMBER_COLLECTION_NAME = "members";
public class MongoMembershipProvider : MembershipProvider
{
private const string MEMBER_COLLECTION_NAME = "members";
private bool _enablePasswordReset = true;
private int _maxInvalidPasswordAttempts = 5;
private bool _enablePasswordReset = true;
private int _maxInvalidPasswordAttempts = 5;
private IMongoCollection<MongoMember> _memberCollection;
private int _minRequiredNonAlphanumericCharacters = 1;
private int _minRequriedPasswordLength = 8;
private int _passwordAttemptWindow = 60;
private bool _requiresUniqueEmail = true;
private IMongoCollection<MongoMember> _memberCollection;
private int _minRequiredNonAlphanumericCharacters = 1;
private int _minRequriedPasswordLength = 8;
private int _passwordAttemptWindow = 60;
private bool _requiresUniqueEmail = true;
public override string ApplicationName { get; set; }
public override string ApplicationName { get; set; }
public override bool EnablePasswordReset => _enablePasswordReset;
public override bool EnablePasswordReset => _enablePasswordReset;
public override bool EnablePasswordRetrieval => false;
public override bool EnablePasswordRetrieval => false;
public override int MaxInvalidPasswordAttempts => _maxInvalidPasswordAttempts;
public override int MaxInvalidPasswordAttempts => _maxInvalidPasswordAttempts;
public override int MinRequiredNonAlphanumericCharacters => _minRequiredNonAlphanumericCharacters;
public override int MinRequiredNonAlphanumericCharacters => _minRequiredNonAlphanumericCharacters;
public override int MinRequiredPasswordLength => _minRequriedPasswordLength;
public override int MinRequiredPasswordLength => _minRequriedPasswordLength;
public override int PasswordAttemptWindow => _passwordAttemptWindow;
public override int PasswordAttemptWindow => _passwordAttemptWindow;
public override MembershipPasswordFormat PasswordFormat => MembershipPasswordFormat.Hashed;
public override MembershipPasswordFormat PasswordFormat => MembershipPasswordFormat.Hashed;
public override string PasswordStrengthRegularExpression => "";
public override string PasswordStrengthRegularExpression => "";
public override bool RequiresQuestionAndAnswer => false;
public override bool RequiresQuestionAndAnswer => false;
public override bool RequiresUniqueEmail => _requiresUniqueEmail;
public override bool RequiresUniqueEmail => _requiresUniqueEmail;
public override void Initialize(string name, NameValueCollection config)
{
if (config == null)
{
throw new ArgumentNullException(nameof(config));
}
base.Initialize(name, config);
_enablePasswordReset = TryReadBool(config["enablePasswordReset"], _enablePasswordReset);
_maxInvalidPasswordAttempts = TryReadInt(config["maxInvalidPasswordAttempts"], _maxInvalidPasswordAttempts);
_minRequiredNonAlphanumericCharacters = TryReadInt(config["minRequiredNonAlphanumericCharacters"], _minRequiredNonAlphanumericCharacters);
_minRequriedPasswordLength = TryReadInt(config["minRequriedPasswordLength"], _minRequriedPasswordLength);
_passwordAttemptWindow = TryReadInt(config["passwordAttemptWindow"], _passwordAttemptWindow);
_requiresUniqueEmail = TryReadBool(config["requiresUniqueEmail"], _requiresUniqueEmail);
MongoClientSettings settings = new MongoClientSettings
{
Server = new MongoServerAddress(DatabaseConfig.Host, DatabaseConfig.Port)
};
if (!string.IsNullOrEmpty(DatabaseConfig.Username) && !string.IsNullOrEmpty(DatabaseConfig.Password))
{
settings.Credentials = new List<MongoCredential>
public override void Initialize(string name, NameValueCollection config)
{
if (config == null)
{
MongoCredential.CreateCredential(DatabaseConfig.Database, DatabaseConfig.Username, DatabaseConfig.Password)
throw new ArgumentNullException(nameof(config));
}
base.Initialize(name, config);
_enablePasswordReset = TryReadBool(config["enablePasswordReset"], _enablePasswordReset);
_maxInvalidPasswordAttempts = TryReadInt(config["maxInvalidPasswordAttempts"], _maxInvalidPasswordAttempts);
_minRequiredNonAlphanumericCharacters = TryReadInt(config["minRequiredNonAlphanumericCharacters"], _minRequiredNonAlphanumericCharacters);
_minRequriedPasswordLength = TryReadInt(config["minRequriedPasswordLength"], _minRequriedPasswordLength);
_passwordAttemptWindow = TryReadInt(config["passwordAttemptWindow"], _passwordAttemptWindow);
_requiresUniqueEmail = TryReadBool(config["requiresUniqueEmail"], _requiresUniqueEmail);
MongoClientSettings settings = new MongoClientSettings
{
Server = new MongoServerAddress(DatabaseConfig.Host, DatabaseConfig.Port)
};
}
MongoClient dbClient = new MongoClient(settings);
if (!string.IsNullOrEmpty(DatabaseConfig.Username) && !string.IsNullOrEmpty(DatabaseConfig.Password))
{
settings.Credentials = new List<MongoCredential>
{
MongoCredential.CreateCredential(DatabaseConfig.Database, DatabaseConfig.Username, DatabaseConfig.Password)
};
}
_memberCollection = dbClient.GetDatabase(DatabaseConfig.Database).GetCollection<MongoMember>(MEMBER_COLLECTION_NAME);
}
MongoClient dbClient = new MongoClient(settings);
public override bool ChangePassword(string username, string oldPassword, string newPassword)
{
bool isAuthenticated = ValidateUser(username, oldPassword);
_memberCollection = dbClient.GetDatabase(DatabaseConfig.Database).GetCollection<MongoMember>(MEMBER_COLLECTION_NAME);
}
if (isAuthenticated)
{
Task<MongoMember> task = _memberCollection.Find(m => m.UserName.ToLower() == username.ToLower()).SingleOrDefaultAsync();
public override bool ChangePassword(string username, string oldPassword, string newPassword)
{
bool isAuthenticated = ValidateUser(username, oldPassword);
if (isAuthenticated)
{
Task<MongoMember> task = _memberCollection.Find(m => m.UserName.ToLower() == username.ToLower()).SingleOrDefaultAsync();
task.Wait();
MongoMember mm = task.Result;
if (mm == null)
{
return false;
}
var salt = new byte[24];
byte[] hash = CalculateHash(newPassword, ref salt);
mm.PassSalt = salt;
mm.PassHash = hash;
Task<ReplaceOneResult> replaceTask = _memberCollection.ReplaceOneAsync(m => m.Id == mm.Id, mm);
replaceTask.Wait();
return replaceTask.IsCompleted;
}
return false;
}
public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
{
throw new NotImplementedException();
}
public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{
if (password.Length < MinRequiredPasswordLength)
{
status = MembershipCreateStatus.InvalidPassword;
return null;
}
MembershipUser mu = null;
Task<long> dupeUsers = _memberCollection.Find(m => m.UserName.ToLower() == username.ToLower()).CountAsync();
Task<long> dupeEmails = _memberCollection.Find(m => m.EmailAddress.ToLower() == email.ToLower()).CountAsync();
dupeUsers.Wait();
dupeEmails.Wait();
if (dupeUsers.Result > 0)
{
status = MembershipCreateStatus.DuplicateUserName;
}
else if (dupeEmails.Result > 0)
{
status = MembershipCreateStatus.DuplicateEmail;
}
else
{
var salt = new byte[24];
byte[] hash = CalculateHash(password, ref salt);
MongoMember mm = new MongoMember
{
Id = Guid.NewGuid(),
UserName = username,
PassHash = hash,
PassSalt = salt,
EmailAddress = email,
IsApproved = isApproved,
IsLockedOut = false,
CreationDate = DateTime.UtcNow,
LastLoginDate = DateTime.MinValue,
LastActivityDate = DateTime.MinValue,
LastLockoutDate = DateTime.MinValue
};
Task insertTask = _memberCollection.InsertOneAsync(mm);
insertTask.Wait();
if (insertTask.Status == TaskStatus.RanToCompletion)
{
status = MembershipCreateStatus.Success;
mu = new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, mm.CreationDate, mm.LastLoginDate, mm.LastActivityDate, DateTime.MinValue, mm.LastLockoutDate);
}
else
{
status = MembershipCreateStatus.ProviderError;
}
}
return mu;
}
public override bool DeleteUser(string username, bool deleteAllRelatedData)
{
Task<DeleteResult> task = _memberCollection.DeleteOneAsync(m => m.UserName.ToLower() == username.ToLower());
task.Wait();
return task.Result.IsAcknowledged && task.Result.DeletedCount == 1;
}
public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
{
throw new NotImplementedException();
}
public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
{
throw new NotImplementedException();
}
public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
{
MembershipUserCollection muc = new MembershipUserCollection();
IFindFluent<MongoMember, MongoMember> users = _memberCollection.Find(new BsonDocument());
Task<long> totalRecordsTask = users.CountAsync();
totalRecordsTask.Wait();
totalRecords = Convert.ToInt32(totalRecordsTask.Result);
users = users.Skip(pageIndex * pageSize).Limit(pageSize);
Task<List<MongoMember>> pageItemsTask = users.ToListAsync();
pageItemsTask.Wait();
foreach (MongoMember mm in pageItemsTask.Result)
{
muc.Add(new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, FixupDatesFromMongo(mm.CreationDate), FixupDatesFromMongo(mm.LastLoginDate), FixupDatesFromMongo(mm.LastActivityDate), DateTime.MinValue, FixupDatesFromMongo(mm.LastLockoutDate)));
}
return muc;
}
public override int GetNumberOfUsersOnline()
{
throw new NotImplementedException();
}
public override string GetPassword(string username, string answer)
{
throw new NotImplementedException();
}
public override MembershipUser GetUser(string username, bool userIsOnline)
{
Task<MongoMember> task = _memberCollection.Find(f => f.UserName.ToLower() == username.ToLower()).FirstOrDefaultAsync();
task.Wait();
MongoMember mm = task.Result;
return mm == null
? null
: new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, FixupDatesFromMongo(mm.CreationDate), FixupDatesFromMongo(mm.LastLoginDate), FixupDatesFromMongo(mm.LastActivityDate), DateTime.MinValue, FixupDatesFromMongo(mm.LastLockoutDate));
}
public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
{
Task<MongoMember> task = _memberCollection.Find(f => f.Id == (Guid)providerUserKey).FirstOrDefaultAsync();
task.Wait();
MongoMember mm = task.Result;
return mm == null
? null
: new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, FixupDatesFromMongo(mm.CreationDate), FixupDatesFromMongo(mm.LastLoginDate), FixupDatesFromMongo(mm.LastActivityDate), DateTime.MinValue, FixupDatesFromMongo(mm.LastLockoutDate));
}
public override string GetUserNameByEmail(string email)
{
Task<MongoMember> task = _memberCollection.Find(f => f.EmailAddress.ToLower() == email.ToLower()).FirstOrDefaultAsync();
task.Wait();
return task.Result.UserName;
}
public override string ResetPassword(string username, string answer)
{
throw new NotImplementedException();
}
public void ChangeApproval(Guid id, bool newStatus)
{
Task<UpdateResult> task = _memberCollection.UpdateOneAsync(Builders<MongoMember>.Filter.Eq(u => u.Id, id), Builders<MongoMember>.Update.Set(u => u.IsApproved, newStatus));
task.Wait();
}
public void ChangeLockStatus(Guid id, bool newStatus)
{
var updateDefinition = new List<UpdateDefinition<MongoMember>>
{
Builders<MongoMember>.Update.Set(u => u.IsLockedOut, newStatus)
};
if (newStatus)
{
updateDefinition.Add(Builders<MongoMember>.Update.Set(u => u.LastLockoutDate, DateTime.UtcNow));
}
else
{
updateDefinition.Add(Builders<MongoMember>.Update.Set(u => u.LockoutWindowAttempts, 0));
updateDefinition.Add(Builders<MongoMember>.Update.Set(u => u.LastLockoutDate, DateTime.MinValue));
}
Task<UpdateResult> task = _memberCollection.UpdateOneAsync(Builders<MongoMember>.Filter.Eq(u => u.Id, id), Builders<MongoMember>.Update.Combine(updateDefinition));
task.Wait();
}
public override bool UnlockUser(string userName)
{
Task<UpdateResult> task = _memberCollection.UpdateOneAsync(Builders<MongoMember>.Filter.Eq(m => m.UserName.ToLower(), userName.ToLower()), Builders<MongoMember>.Update.Set(m => m.IsLockedOut, false));
task.Wait();
return task.Result.IsAcknowledged && task.Result.ModifiedCount == 1;
}
public override void UpdateUser(MembershipUser user)
{
throw new NotImplementedException();
}
public override bool ValidateUser(string username, string password)
{
Task<MongoMember> task = _memberCollection.Find(f => f.UserName.ToLower() == username.ToLower()).FirstOrDefaultAsync();
task.Wait();
MongoMember mm = task.Result;
if (mm == null)
if (mm == null
|| !(mm.IsApproved && !mm.IsLockedOut))
{
return false;
return false;
}
var salt = new byte[24];
byte[] hash = CalculateHash(newPassword, ref salt);
mm.PassSalt = salt;
mm.PassHash = hash;
Task<ReplaceOneResult> replaceTask = _memberCollection.ReplaceOneAsync(m => m.Id == mm.Id, mm);
replaceTask.Wait();
return replaceTask.IsCompleted;
}
return false;
}
public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
{
throw new NotImplementedException();
}
public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{
if (password.Length < MinRequiredPasswordLength)
{
status = MembershipCreateStatus.InvalidPassword;
return null;
}
MembershipUser mu = null;
Task<long> dupeUsers = _memberCollection.Find(m => m.UserName.ToLower() == username.ToLower()).CountAsync();
Task<long> dupeEmails = _memberCollection.Find(m => m.EmailAddress.ToLower() == email.ToLower()).CountAsync();
dupeUsers.Wait();
dupeEmails.Wait();
if (dupeUsers.Result > 0)
{
status = MembershipCreateStatus.DuplicateUserName;
}
else if (dupeEmails.Result > 0)
{
status = MembershipCreateStatus.DuplicateEmail;
}
else
{
var salt = new byte[24];
byte[] salt = mm.PassSalt;
byte[] hash = CalculateHash(password, ref salt);
MongoMember mm = new MongoMember
{
Id = Guid.NewGuid(),
UserName = username,
PassHash = hash,
PassSalt = salt,
EmailAddress = email,
IsApproved = false,
IsLockedOut = false,
CreationDate = DateTime.Now,
LastLoginDate = DateTime.MinValue,
LastActivityDate = DateTime.MinValue,
LastLockoutDate = DateTime.MinValue
};
bool isFail = false;
Task insertTask = _memberCollection.InsertOneAsync(mm);
insertTask.Wait();
if (insertTask.Status == TaskStatus.RanToCompletion)
for (int i = 0; i > hash.Length; i++)
{
status = MembershipCreateStatus.Success;
mu = new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, mm.CreationDate, mm.LastLoginDate, mm.LastActivityDate, DateTime.MinValue, mm.LastLockoutDate);
isFail |= hash[i] != mm.PassHash[i];
}
if (isFail)
{
if (mm.LockoutWindowStart == DateTime.MinValue)
{
mm.LockoutWindowStart = DateTime.UtcNow;
mm.LockoutWindowAttempts = 1;
}
else
{
if (mm.LockoutWindowStart.AddMinutes(PasswordAttemptWindow) > DateTime.UtcNow)
{
// still within window
mm.LockoutWindowAttempts++;
if (mm.LockoutWindowAttempts >= MaxInvalidPasswordAttempts)
{
mm.IsLockedOut = true;
}
}
else
{
// outside of window, reset
mm.LockoutWindowStart = DateTime.UtcNow;
mm.LockoutWindowAttempts = 1;
}
}
}
else
{
status = MembershipCreateStatus.ProviderError;
mm.LastLoginDate = DateTime.UtcNow;
mm.LockoutWindowStart = DateTime.MinValue;
mm.LockoutWindowAttempts = 0;
}
}
return mu;
}
Task<ReplaceOneResult> updTask = _memberCollection.ReplaceOneAsync(Builders<MongoMember>.Filter.Eq(u => u.Id, mm.Id), mm);
updTask.Wait();
public override bool DeleteUser(string username, bool deleteAllRelatedData)
{
Task<DeleteResult> task = _memberCollection.DeleteOneAsync(m => m.UserName.ToLower() == username.ToLower());
task.Wait();
return !isFail;
}
return task.Result.IsAcknowledged && (task.Result.DeletedCount == 1);
}
public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
{
throw new NotImplementedException();
}
public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
{
throw new NotImplementedException();
}
public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
{
MembershipUserCollection muc = new MembershipUserCollection();
IFindFluent<MongoMember, MongoMember> users = _memberCollection.Find(new BsonDocument());
Task<long> totalRecordsTask = users.CountAsync();
totalRecordsTask.Wait();
totalRecords = Convert.ToInt32(totalRecordsTask.Result);
users = users.Skip(pageIndex * pageSize).Limit(pageSize);
Task<List<MongoMember>> pageItemsTask = users.ToListAsync();
pageItemsTask.Wait();
foreach (MongoMember mm in pageItemsTask.Result)
{
muc.Add(new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, mm.CreationDate, mm.LastLoginDate, mm.LastActivityDate, DateTime.MinValue, mm.LastLockoutDate));
}
return muc;
}
public override int GetNumberOfUsersOnline()
{
throw new NotImplementedException();
}
public override string GetPassword(string username, string answer)
{
throw new NotImplementedException();
}
public override MembershipUser GetUser(string username, bool userIsOnline)
{
Task<MongoMember> task = _memberCollection.Find(f => f.UserName.ToLower() == username.ToLower()).FirstOrDefaultAsync();
task.Wait();
MongoMember mm = task.Result;
return mm == null
? null
: new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, mm.CreationDate, mm.LastLoginDate, mm.LastActivityDate, DateTime.MinValue, mm.LastLockoutDate);
}
public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
{
Task<MongoMember> task = _memberCollection.Find(f => f.Id == (Guid)providerUserKey).FirstOrDefaultAsync();
task.Wait();
MongoMember mm = task.Result;
return mm == null
? null
: new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, mm.CreationDate, mm.LastLoginDate, mm.LastActivityDate, DateTime.MinValue, mm.LastLockoutDate);
}
public override string GetUserNameByEmail(string email)
{
Task<MongoMember> task = _memberCollection.Find(f => f.EmailAddress.ToLower() == email.ToLower()).FirstOrDefaultAsync();
task.Wait();
return task.Result.UserName;
}
public override string ResetPassword(string username, string answer)
{
throw new NotImplementedException();
}
public void ChangeApproval(Guid id, bool newStatus)
{
Task<UpdateResult> task = _memberCollection.UpdateOneAsync(Builders<MongoMember>.Filter.Eq(u => u.Id, id), Builders<MongoMember>.Update.Set(u => u.IsApproved, newStatus));
task.Wait();
}
public void ChangeLockStatus(Guid id, bool newStatus)
{
var updateDefinition = new List<UpdateDefinition<MongoMember>>
{
Builders<MongoMember>.Update.Set(u => u.IsLockedOut, newStatus)
};
if (newStatus)
{
updateDefinition.Add(Builders<MongoMember>.Update.Set(u => u.LastLockoutDate, DateTime.Now));
}
else
{
updateDefinition.Add(Builders<MongoMember>.Update.Set(u => u.LockoutWindowAttempts, 0));
updateDefinition.Add(Builders<MongoMember>.Update.Set(u => u.LastLockoutDate, DateTime.MinValue));
}
Task<UpdateResult> task = _memberCollection.UpdateOneAsync(Builders<MongoMember>.Filter.Eq(u => u.Id, id), Builders<MongoMember>.Update.Combine(updateDefinition));
task.Wait();
}
public override bool UnlockUser(string userName)
{
Task<UpdateResult> task = _memberCollection.UpdateOneAsync(Builders<MongoMember>.Filter.Eq(m => m.UserName.ToLower(), userName.ToLower()), Builders<MongoMember>.Update.Set(m => m.IsLockedOut, false));
task.Wait();
return task.Result.IsAcknowledged && (task.Result.ModifiedCount == 1);
}
public override void UpdateUser(MembershipUser user)
{
throw new NotImplementedException();
}
public override bool ValidateUser(string username, string password)
{
Task<MongoMember> task = _memberCollection.Find(f => f.UserName.ToLower() == username.ToLower()).FirstOrDefaultAsync();
task.Wait();
MongoMember mm = task.Result;
if ((mm == null)
|| !(mm.IsApproved && !mm.IsLockedOut))
{
return false;
}
byte[] salt = mm.PassSalt;
byte[] hash = CalculateHash(password, ref salt);
bool isFail = false;
for (int i = 0; i > hash.Length; i++)
{
isFail |= hash[i] != mm.PassHash[i];
}
if (isFail)
{
if (mm.LockoutWindowStart == DateTime.MinValue)
public async Task<string> GenerateValidationHash(Guid id)
{
MongoMember mm = await _memberCollection.Find(Builders<MongoMember>.Filter.Eq(u => u.Id, id)).FirstOrDefaultAsync();
if (mm == null)
{
mm.LockoutWindowStart = DateTime.Now;
mm.LockoutWindowAttempts = 1;
return null;
}
else
using (SHA256 sha = SHA256.Create())
{
if (mm.LockoutWindowStart.AddMinutes(PasswordAttemptWindow) > DateTime.Now)
{
// still within window
mm.LockoutWindowAttempts++;
if (mm.LockoutWindowAttempts >= MaxInvalidPasswordAttempts)
{
mm.IsLockedOut = true;
}
}
else
{
// outside of window, reset
mm.LockoutWindowStart = DateTime.Now;
mm.LockoutWindowAttempts = 1;
}
string content = $"{mm.Id}.{mm.PassSalt}.{ConfigurationManager.AppSettings["data:SecretKey"]}";
byte[] hashBytes = sha.ComputeHash(Encoding.UTF8.GetBytes(content));
return Base32Encoding.ToString(hashBytes);
}
}
else
{
mm.LastLoginDate = DateTime.Now;
mm.LockoutWindowStart = DateTime.MinValue;
mm.LockoutWindowAttempts = 0;
}
}
Task<ReplaceOneResult> updTask = _memberCollection.ReplaceOneAsync(Builders<MongoMember>.Filter.Eq(u => u.Id, mm.Id), mm);
updTask.Wait();
public async Task<bool> ValidateUserFromHash(Guid id, string validate)
{
MongoMember mm = await _memberCollection.Find(Builders<MongoMember>.Filter.Eq(u => u.Id, id)).FirstOrDefaultAsync();
if (mm == null)
{
return false;
}
return !isFail;
}
using (SHA256 sha = SHA256.Create())
{
string content = $"{mm.Id}.{mm.PassSalt}.{ConfigurationManager.AppSettings["data:SecretKey"]}";
byte[] hashBytes = sha.ComputeHash(Encoding.UTF8.GetBytes(content));
private static byte[] CalculateHash(string password, ref byte[] salt)
{
if (!salt.Any(v => v != 0))
{
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
rng.GetBytes(salt);
}
string expected = Base32Encoding.ToString(hashBytes);
byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
bool success = string.Equals(expected, validate, StringComparison.InvariantCultureIgnoreCase);
var hashPlaintext = new byte[salt.Length + passwordBytes.Length];
if (success)
{
ChangeApproval(id, true);
}
passwordBytes.CopyTo(hashPlaintext, 0);
salt.CopyTo(hashPlaintext, passwordBytes.Length);
return success;
}
}
SHA512CryptoServiceProvider sha = new SHA512CryptoServiceProvider();
byte[] hash = sha.ComputeHash(hashPlaintext);
private static byte[] CalculateHash(string password, ref byte[] salt)
{
if (!salt.Any(v => v != 0))
{
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
rng.GetBytes(salt);
}
return hash;
}
byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
private static bool TryReadBool(string config, bool defaultValue)
{
bool temp;
bool success = bool.TryParse(config, out temp);
return success
? temp
: defaultValue;
}
var hashPlaintext = new byte[salt.Length + passwordBytes.Length];
private static int TryReadInt(string config, int defaultValue)
{
int temp;
bool success = int.TryParse(config, out temp);
return success
? temp
: defaultValue;
}
}
passwordBytes.CopyTo(hashPlaintext, 0);
salt.CopyTo(hashPlaintext, passwordBytes.Length);
public class MongoMember
{
[BsonId]
public Guid Id { get; set; }
SHA512CryptoServiceProvider sha = new SHA512CryptoServiceProvider();
byte[] hash = sha.ComputeHash(hashPlaintext);
public string UserName { get; set; }
public byte[] PassHash { get; set; }
public byte[] PassSalt { get; set; }
public string EmailAddress { get; set; }
return hash;
}
public bool IsApproved { get; set; }
public bool IsLockedOut { get; set; }
private static bool TryReadBool(string config, bool defaultValue)
{
bool temp;
bool success = bool.TryParse(config, out temp);
return success
? temp
: defaultValue;
}
public DateTime CreationDate { get; set; }
public DateTime LastActivityDate { get; set; }
public DateTime LastLockoutDate { get; set; }
public DateTime LastLoginDate { get; set; }
private static int TryReadInt(string config, int defaultValue)
{
int temp;
bool success = int.TryParse(config, out temp);
return success
? temp
: defaultValue;
}
public DateTime LockoutWindowStart { get; set; }
public int LockoutWindowAttempts { get; set; }
}
private static DateTime FixupDatesFromMongo(DateTime dt)
{
DateTime local = DateTime.SpecifyKind(dt, DateTimeKind.Local);
return local;
}
}
public class MongoMember
{
[BsonId]
public Guid Id { get; set; }
public string UserName { get; set; }
public byte[] PassHash { get; set; }
public byte[] PassSalt { get; set; }
public string EmailAddress { get; set; }
public bool IsApproved { get; set; }
public bool IsLockedOut { get; set; }
public DateTime CreationDate { get; set; }
public DateTime LastActivityDate { get; set; }
public DateTime LastLockoutDate { get; set; }
public DateTime LastLoginDate { get; set; }
public DateTime LockoutWindowStart { get; set; }
public int LockoutWindowAttempts { get; set; }
}
}

View File

@ -13,214 +13,221 @@
namespace MongoAuth
{
public class MongoRoleProvider : RoleProvider
{
private const string MEMBER_COLLECTION_NAME = "members";
private const string ROLE_COLLECTION_NAME = "roles";
private IMongoCollection<MongoMember> _memberCollection;
private IMongoCollection<MongoRole> _roleCollection;
public class MongoRoleProvider : RoleProvider
{
private const string MEMBER_COLLECTION_NAME = "members";
private const string ROLE_COLLECTION_NAME = "roles";
private IMongoCollection<MongoMember> _memberCollection;
private IMongoCollection<MongoRole> _roleCollection;
public override string ApplicationName
{
get { return ""; }
set { }
}
public override string ApplicationName
{
get { return ""; }
set { }
}
public override void Initialize(string name, NameValueCollection config)
{
base.Initialize(name, config);
public override void Initialize(string name, NameValueCollection config)
{
base.Initialize(name, config);
MongoClientSettings settings = new MongoClientSettings
{
Server = new MongoServerAddress(DatabaseConfig.Host, DatabaseConfig.Port)
};
if (!string.IsNullOrEmpty(DatabaseConfig.Username) && !string.IsNullOrEmpty(DatabaseConfig.Password))
{
settings.Credentials = new List<MongoCredential>
MongoClientSettings settings = new MongoClientSettings
{
MongoCredential.CreateCredential(DatabaseConfig.Database, DatabaseConfig.Username, DatabaseConfig.Password)
Server = new MongoServerAddress(DatabaseConfig.Host, DatabaseConfig.Port)
};
}
MongoClient dbClient = new MongoClient(settings);
_roleCollection = dbClient.GetDatabase(DatabaseConfig.Database).GetCollection<MongoRole>(ROLE_COLLECTION_NAME);
_memberCollection = dbClient.GetDatabase(DatabaseConfig.Database).GetCollection<MongoMember>(MEMBER_COLLECTION_NAME);
}
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
{
Task<List<MongoRole>> roleTask = _roleCollection.Find(r => roleNames.Contains(r.RoleName)).ToListAsync();
roleTask.Wait();
List<MongoRole> roles = roleTask.Result;
Task<List<MongoMember>> userTask = _memberCollection.Find(u => usernames.Contains(u.UserName)).ToListAsync();
userTask.Wait();
List<MongoMember> users = userTask.Result;
for (int i = 0; i < roles.Count; i++)
{
var newUsers = new List<Guid>();
if (roles[i].Users != null)
if (!string.IsNullOrEmpty(DatabaseConfig.Username) && !string.IsNullOrEmpty(DatabaseConfig.Password))
{
newUsers.AddRange(roles[i].Users);
settings.Credentials = new List<MongoCredential>
{
MongoCredential.CreateCredential(DatabaseConfig.Database, DatabaseConfig.Username, DatabaseConfig.Password)
};
}
IEnumerable<Guid> usersToAdd = from u in users
where newUsers.All(v => v != u.Id)
select u.Id;
MongoClient dbClient = new MongoClient(settings);
newUsers.AddRange(usersToAdd);
_roleCollection = dbClient.GetDatabase(DatabaseConfig.Database).GetCollection<MongoRole>(ROLE_COLLECTION_NAME);
_memberCollection = dbClient.GetDatabase(DatabaseConfig.Database).GetCollection<MongoMember>(MEMBER_COLLECTION_NAME);
}
roles[i].Users = newUsers.ToArray();
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
{
Task<List<MongoRole>> roleTask = _roleCollection.Find(r => roleNames.Contains(r.RoleName)).ToListAsync();
roleTask.Wait();
List<MongoRole> roles = roleTask.Result;
Task<ReplaceOneResult> update = _roleCollection.ReplaceOneAsync(Builders<MongoRole>.Filter.Eq(r => r.Id, roles[i].Id), roles[i]);
update.Wait();
}
}
Task<List<MongoMember>> userTask = _memberCollection.Find(u => usernames.Contains(u.UserName)).ToListAsync();
userTask.Wait();
List<MongoMember> users = userTask.Result;
public override void CreateRole(string roleName)
{
MongoRole r = new MongoRole
{
Id = Guid.NewGuid(),
RoleName = roleName
};
for (int i = 0; i < roles.Count; i++)
{
var newUsers = new List<Guid>();
Task task = _roleCollection.InsertOneAsync(r);
task.Wait();
}
if (roles[i].Users != null)
{
newUsers.AddRange(roles[i].Users);
}
public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
{
Task<MongoRole> role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync();
role.Wait();
IEnumerable<Guid> usersToAdd = from u in users
where newUsers.All(v => v != u.Id)
select u.Id;
if ((role.Result != null)
&& (role.Result.Users.Length > 0)
&& throwOnPopulatedRole)
{
throw new ProviderException(Resources.RoleNotEmpty);
}
newUsers.AddRange(usersToAdd);
Task<DeleteResult> task = _roleCollection.DeleteOneAsync(r => r.RoleName == roleName);
task.Wait();
roles[i].Users = newUsers.ToArray();
return true;
}
Task<ReplaceOneResult> update = _roleCollection.ReplaceOneAsync(Builders<MongoRole>.Filter.Eq(r => r.Id, roles[i].Id), roles[i]);
update.Wait();
}
}
public override string[] FindUsersInRole(string roleName, string usernameToMatch)
{
Task<MongoRole> role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync();
role.Wait();
public override void CreateRole(string roleName)
{
Task<MongoRole> role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync();
role.Wait();
if (role.Result != null)
{
return;
}
if (role.Result == null)
{
return Array.Empty<string>();
}
MongoRole mr = new MongoRole
{
Id = Guid.NewGuid(),
RoleName = roleName
};
Task<List<MongoMember>> users = _memberCollection.Find(u => role.Result.Users.Contains(u.Id) && u.UserName.ToLower().Contains(usernameToMatch.ToLower())).ToListAsync();
users.Wait();
Task task = _roleCollection.InsertOneAsync(mr);
task.Wait();
}
return users.Result.Select(r => r.UserName).ToArray();
}
public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
{
Task<MongoRole> role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync();
role.Wait();
public override string[] GetAllRoles()
{
Task<List<MongoRole>> roles = _roleCollection.Find(new BsonDocument()).ToListAsync();
roles.Wait();
if (role.Result != null
&& role.Result.Users.Length > 0
&& throwOnPopulatedRole)
{
throw new ProviderException(Resources.RoleNotEmpty);
}
return roles.Result.Select(r => r.RoleName).ToArray();
}
Task<DeleteResult> task = _roleCollection.DeleteOneAsync(r => r.RoleName == roleName);
task.Wait();
public override string[] GetRolesForUser(string username)
{
Task<MongoMember> user = _memberCollection.Find(u => u.UserName.ToLower() == username.ToLower()).SingleOrDefaultAsync();
user.Wait();
return true;
}
if (user.Result == null)
{
return Array.Empty<string>();
}
public override string[] FindUsersInRole(string roleName, string usernameToMatch)
{
Task<MongoRole> role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync();
role.Wait();
Task<List<MongoRole>> role = _roleCollection.Find(r => (r.Users != null) && r.Users.Contains(user.Result.Id)).ToListAsync();
role.Wait();
if (role.Result == null)
{
return Array.Empty<string>();
}
return role.Result.Select(r => r.RoleName).ToArray();
}
Task<List<MongoMember>> users = _memberCollection.Find(u => role.Result.Users.Contains(u.Id) && u.UserName.ToLower().Contains(usernameToMatch.ToLower())).ToListAsync();
users.Wait();
public override string[] GetUsersInRole(string roleName)
{
Task<MongoRole> role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync();
role.Wait();
return users.Result.Select(r => r.UserName).ToArray();
}
if (role.Result == null)
{
return Array.Empty<string>();
}
public override string[] GetAllRoles()
{
Task<List<MongoRole>> roles = _roleCollection.Find(new BsonDocument()).ToListAsync();
roles.Wait();
Task<List<MongoMember>> users = _memberCollection.Find(u => role.Result.Users.Contains(u.Id)).ToListAsync();
users.Wait();
return roles.Result.Select(r => r.RoleName).ToArray();
}
return users.Result.Select(u => u.UserName).ToArray();
}
public override string[] GetRolesForUser(string username)
{
Task<MongoMember> user = _memberCollection.Find(u => u.UserName.ToLower() == username.ToLower()).SingleOrDefaultAsync();
user.Wait();
public override bool IsUserInRole(string username, string roleName)
{
Task<MongoMember> user = _memberCollection.Find(u => u.UserName.ToLower() == username.ToLower()).SingleOrDefaultAsync();
Task<MongoRole> role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync();
user.Wait();
role.Wait();
if (user.Result == null)
{
return Array.Empty<string>();
}
if ((user.Result == null)
|| (role.Result?.Users == null))
{
return false;
}
Task<List<MongoRole>> role = _roleCollection.Find(r => r.Users != null && r.Users.Contains(user.Result.Id)).ToListAsync();
role.Wait();
return role.Result.Users.Contains(user.Result.Id);
}
return role.Result.Select(r => r.RoleName).ToArray();
}
public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
{
Task<List<MongoRole>> roleTask = _roleCollection.Find(r => roleNames.Contains(r.RoleName)).ToListAsync();
roleTask.Wait();
List<MongoRole> roles = roleTask.Result;
public override string[] GetUsersInRole(string roleName)
{
Task<MongoRole> role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync();
role.Wait();
Task<List<MongoMember>> userTask = _memberCollection.Find(u => usernames.Contains(u.UserName)).ToListAsync();
userTask.Wait();
List<MongoMember> users = userTask.Result;
if (role.Result == null)
{
return Array.Empty<string>();
}
foreach (MongoRole t in roles)
{
t.Users = (from u in t.Users
where users.All(v => v.Id != u)
select u).ToArray();
Task<List<MongoMember>> users = _memberCollection.Find(u => role.Result.Users.Contains(u.Id)).ToListAsync();
users.Wait();
Task<ReplaceOneResult> update = _roleCollection.ReplaceOneAsync(Builders<MongoRole>.Filter.Eq(r => r.Id, t.Id), t);
update.Wait();
}
}
return users.Result.Select(u => u.UserName).ToArray();
}
public override bool RoleExists(string roleName)
{
Task<MongoRole> role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync();
role.Wait();
public override bool IsUserInRole(string username, string roleName)
{
Task<MongoMember> user = _memberCollection.Find(u => u.UserName.ToLower() == username.ToLower()).SingleOrDefaultAsync();
Task<MongoRole> role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync();
user.Wait();
role.Wait();
return role.Result != null;
}
}
if (user.Result == null
|| role.Result?.Users == null)
{
return false;
}
[DataObject]
public class MongoRole
{
[BsonId]
public Guid Id { get; set; }
return role.Result.Users.Contains(user.Result.Id);
}
public string RoleName { get; set; }
public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
{
Task<List<MongoRole>> roleTask = _roleCollection.Find(r => roleNames.Contains(r.RoleName)).ToListAsync();
roleTask.Wait();
List<MongoRole> roles = roleTask.Result;
public Guid[] Users { get; set; }
}
Task<List<MongoMember>> userTask = _memberCollection.Find(u => usernames.Contains(u.UserName)).ToListAsync();
userTask.Wait();
List<MongoMember> users = userTask.Result;
foreach (MongoRole t in roles)
{
t.Users = (from u in t.Users
where users.All(v => v.Id != u)
select u).ToArray();
Task<ReplaceOneResult> update = _roleCollection.ReplaceOneAsync(Builders<MongoRole>.Filter.Eq(r => r.Id, t.Id), t);
update.Wait();
}
}
public override bool RoleExists(string roleName)
{
Task<MongoRole> role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync();
role.Wait();
return role.Result != null;
}
}
[DataObject]
public class MongoRole
{
[BsonId]
public Guid Id { get; set; }
public string RoleName { get; set; }
public Guid[] Users { get; set; }
}
}

View File

@ -186,6 +186,15 @@ public static string Common_Admin {
}
}
/// <summary>
/// Looks up a localized string similar to Change password.
/// </summary>
public static string Common_ChangePassword {
get {
return ResourceManager.GetString("Common_ChangePassword", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Contribute on {0}.
/// </summary>
@ -330,6 +339,32 @@ public static string Common_Twitter {
}
}
/// <summary>
/// Looks up a localized string similar to Thank you for registering to {0}.
///
///Please verify your email address by clicking the link below, or by copying and pasting it into your browser.
///{1}
///
///If you did not register to {0}, you can ignore this email.
///
///Thanks,
///The {0} Team..
/// </summary>
public static string Email_Registration_Body {
get {
return ResourceManager.GetString("Email_Registration_Body", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0}: Please verify your email address.
/// </summary>
public static string Email_Registration_Subject {
get {
return ResourceManager.GetString("Email_Registration_Subject", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to About.
/// </summary>
@ -924,15 +959,6 @@ public static string Search_Year {
}
}
/// <summary>
/// Looks up a localized string similar to Every account is validated by an administrator, so be patient and check again later.
/// </summary>
public static string Support_AccountValidation {
get {
return ResourceManager.GetString("Support_AccountValidation", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Additions to BuildFeed.
/// </summary>
@ -987,6 +1013,24 @@ public static string Support_EmailAddress {
}
}
/// <summary>
/// Looks up a localized string similar to An validation link has been sent to your email address. Please click the link in the email to verify the account..
/// </summary>
public static string Support_EmailValidationContent {
get {
return ResourceManager.GetString("Support_EmailValidationContent", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Please verify your email address.
/// </summary>
public static string Support_EmailValidationTitle {
get {
return ResourceManager.GetString("Support_EmailValidationTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Enter current password.
/// </summary>
@ -1014,6 +1058,51 @@ public static string Support_EnterPassword {
}
}
/// <summary>
/// Looks up a localized string similar to An error occurred when attempting to change your password..
/// </summary>
public static string Support_Error_ChangingPasswordFail {
get {
return ResourceManager.GetString("Support_Error_ChangingPasswordFail", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to A user account with this email address already exists..
/// </summary>
public static string Support_Error_DuplicateEmail {
get {
return ResourceManager.GetString("Support_Error_DuplicateEmail", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to A user account with this user name already exists..
/// </summary>
public static string Support_Error_DuplicateUserName {
get {
return ResourceManager.GetString("Support_Error_DuplicateUserName", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The password you have entered is invalid..
/// </summary>
public static string Support_Error_InvalidPassword {
get {
return ResourceManager.GetString("Support_Error_InvalidPassword", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to An unknown error has occurred..
/// </summary>
public static string Support_Error_UnknownError {
get {
return ResourceManager.GetString("Support_Error_UnknownError", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Highest version.
/// </summary>
@ -1095,15 +1184,6 @@ public static string Support_RememberMe {
}
}
/// <summary>
/// Looks up a localized string similar to Thank you for registering.
/// </summary>
public static string Support_ThanksRegister {
get {
return ResourceManager.GetString("Support_ThanksRegister", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Username.
/// </summary>
@ -1113,6 +1193,42 @@ public static string Support_UserName {
}
}
/// <summary>
/// Looks up a localized string similar to Please check the validation link in your email and try again..
/// </summary>
public static string Support_ValidationFailureContent {
get {
return ResourceManager.GetString("Support_ValidationFailureContent", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to We were unable to validate your account..
/// </summary>
public static string Support_ValidationFailureTitle {
get {
return ResourceManager.GetString("Support_ValidationFailureTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You will now be able to log in using your username and password..
/// </summary>
public static string Support_ValidationSuccessContent {
get {
return ResourceManager.GetString("Support_ValidationSuccessContent", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You have successfully validated your account..
/// </summary>
public static string Support_ValidationSuccessTitle {
get {
return ResourceManager.GetString("Support_ValidationSuccessTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Week.
/// </summary>

View File

@ -159,6 +159,9 @@
<data name="Common_Admin" xml:space="preserve">
<value>[!!! Âδ₥ïñ !!!]</value>
</data>
<data name="Common_ChangePassword" xml:space="preserve">
<value>[!!! Çλáñϱè ƥáƨƨωôřδ !!!]</value>
</data>
<data name="Common_ContributeOn" xml:space="preserve">
<value>[!!! Çôñƭřïβúƭè ôñ {0} !!!]</value>
</data>
@ -207,6 +210,20 @@
<data name="Common_Twitter" xml:space="preserve">
<value>[!!! Tωïƭƭèř !!!]</value>
</data>
<data name="Email_Registration_Body" xml:space="preserve">
<value>[!!! Tλáñƙ ¥ôú ƒôř řèϱïƨƭèřïñϱ ƭô {0}. ℓôřè !!!]
[!!! !!!]
[!!! Þℓèáƨè Ʋèř ¥ôúř è₥áïℓ áδδřèƨƨ β¥ çℓïçƙïñϱ ƭλè ℓïñƙ βèℓôω, ôř β¥ çôƥ¥ïñϱ áñδ ƥáƨƭïñϱ ïƭ ïñƭô ¥ôúř βřôωƨèř. ℓôřè₥ ïƥƨú₥ δôℓ !!!]
[!!! {1} !!!]
[!!! !!!]
[!!! ̃ ¥ôú δïδ ñôƭ řèϱïƨƭèř ƭô {0}, ¥ôú çáñ ïϱñôřè ƭλïƨ è₥áïℓ. ℓôřè₥ ïƥ !!!]
[!!! !!!]
[!!! Tλáñƙƨ, !!!]
[!!! Tλè {0} Tèá₥. !!!]</value>
</data>
<data name="Email_Registration_Subject" xml:space="preserve">
<value>[!!! {0}: Þℓèáƨè Ʋèř ¥ôúř è₥áïℓ áδδřèƨƨ ℓôřè₥ !!!]</value>
</data>
<data name="Front_About" xml:space="preserve">
<value>[!!! Âβôúƭ !!!]</value>
</data>
@ -405,9 +422,6 @@
<data name="Search_Year" xml:space="preserve">
<value>[!!! Ýèář !!!]</value>
</data>
<data name="Support_AccountValidation" xml:space="preserve">
<value>[!!! ÉƲèř¥ áççôúñƭ ïƨ Ʋáℓïδáƭèδ β¥ áñ Âδ₥ïñïƨƭřáƭôř, ƨô βè ƥáƭïèñƭ áñδ çλèçƙ áϱáïñ ℓáƭèř. ℓôřè₥ ï !!!]</value>
</data>
<data name="Support_AdditionsToBuildFeed" xml:space="preserve">
<value>[!!! Âδδïƭïôñƨ ƭô ßúïδFèèδ !!!]</value>
</data>
@ -426,6 +440,12 @@
<data name="Support_EmailAddress" xml:space="preserve">
<value>[!!! É₥áïℓ áδδřèƨƨ !!!]</value>
</data>
<data name="Support_EmailValidationContent" xml:space="preserve">
<value>[!!! Âñ Ʋáℓïδáƭïôñ ℓïñƙ λáƨ βèèñ ƨèñƭ ƭô ¥ôúř è₥áïℓ áδδřèƨƨ. Þℓèáƨè çℓïçƙ ƭλè ℓïñƙ ïñ ƭλè è₥áïℓ ƭô Ʋèř ƭλè áççôúñƭ. ℓôřè₥ ïƥƨú₥ δôℓô !!!]</value>
</data>
<data name="Support_EmailValidationTitle" xml:space="preserve">
<value>[!!! Þℓèáƨè Ʋèř ¥ôúř è₥áïℓ áδδřèƨƨ ℓôřè !!!]</value>
</data>
<data name="Support_EnterCurrentPassword" xml:space="preserve">
<value>[!!! Éñƭèř çúřřèñƭ ƥáƨƨωôřδ !!!]</value>
</data>
@ -435,6 +455,21 @@
<data name="Support_EnterPassword" xml:space="preserve">
<value>[!!! Éñƭèř ƥáƨƨωôřδ !!!]</value>
</data>
<data name="Support_Error_ChangingPasswordFail" xml:space="preserve">
<value>[!!! Âñ èřřôř ôççúřřèδ ωλèñ áƭƭè₥ƥƭïñϱ ƭô çλáñϱè ¥ôúř ƥáƨƨωôřδ. ℓôřè₥ ïƥ !!!]</value>
</data>
<data name="Support_Error_DuplicateEmail" xml:space="preserve">
<value>[!!! Â úƨèř áççôúñƭ ωïƭλ ƭλïƨ è₥áïℓ áδδřèƨƨ áℓřèáδ¥ èжïƨƭƨ. ℓôřè₥ ï !!!]</value>
</data>
<data name="Support_Error_DuplicateUserName" xml:space="preserve">
<value>[!!! Â úƨèř áççôúñƭ ωïƭλ ƭλïƨ úƨèř ñá₥è áℓřèáδ¥ èжïƨƭƨ. ℓôřè₥ ï !!!]</value>
</data>
<data name="Support_Error_InvalidPassword" xml:space="preserve">
<value>[!!! Tλè ƥáƨƨωôřδ ¥ôú λáƲè èñƭèřèδ ïƨ ïñƲáℓïδ. ℓôřè₥ !!!]</value>
</data>
<data name="Support_Error_UnknownError" xml:space="preserve">
<value>[!!! Âñ úñƙñôωñ èřřôř λáƨ ôççúřřèδ. ℓôřè !!!]</value>
</data>
<data name="Support_HighestVersion" xml:space="preserve">
<value>[!!! Hïϱλèƨƭ Ʋèřƨïôñ !!!]</value>
</data>
@ -462,12 +497,21 @@
<data name="Support_RememberMe" xml:space="preserve">
<value>[!!! Rè₥è₥βèř ₥è !!!]</value>
</data>
<data name="Support_ThanksRegister" xml:space="preserve">
<value>[!!! Tλáñƙ ¥ôú ƒôř řèϱïƨƭèřïñϱ ℓô !!!]</value>
</data>
<data name="Support_UserName" xml:space="preserve">
<value>[!!! Ûƨèřñá₥è !!!]</value>
</data>
<data name="Support_ValidationFailureContent" xml:space="preserve">
<value>[!!! Þℓèáƨè çλèçƙ ƭλè Ʋáℓïδáƭïôñ ℓïñƙ ïñ ¥ôúř è₥áïℓ áñδ ƭř¥ áϱáïñ. ℓôřè₥ ïƥ !!!]</value>
</data>
<data name="Support_ValidationFailureTitle" xml:space="preserve">
<value>[!!! Wè ωèřè úñáβℓè ƭô Ʋáℓïδáƭè ¥ôúř áççôúñƭ. ℓôřè₥ !!!]</value>
</data>
<data name="Support_ValidationSuccessContent" xml:space="preserve">
<value>[!!! Ýôú ωïℓℓ ñôω βè áβℓè ƭô ℓôϱ ïñ úƨïñϱ ¥ôúř úƨèřñá₥è áñδ ƥáƨƨωôřδ. ℓôřè₥ ïƥƨ !!!]</value>
</data>
<data name="Support_ValidationSuccessTitle" xml:space="preserve">
<value>[!!! Ýôú λáƲè ƨúççèƨƨƒúℓℓ¥ Ʋáℓïδáƭèδ ¥ôúř áççôúñƭ. ℓôřè₥ !!!]</value>
</data>
<data name="Support_Week" xml:space="preserve">
<value>[!!! Wèèƙ !!!]</value>
</data>

View File

@ -159,6 +159,9 @@
<data name="Common_Admin" xml:space="preserve">
<value>Admin</value>
</data>
<data name="Common_ChangePassword" xml:space="preserve">
<value>Change password</value>
</data>
<data name="Common_ContributeOn" xml:space="preserve">
<value>Contribute on {0}</value>
</data>
@ -207,6 +210,20 @@
<data name="Common_Twitter" xml:space="preserve">
<value>Twitter</value>
</data>
<data name="Email_Registration_Body" xml:space="preserve">
<value>Thank you for registering to {0}.
Please verify your email address by clicking the link below, or by copying and pasting it into your browser.
{1}
If you did not register to {0}, you can ignore this email.
Thanks,
The {0} Team.</value>
</data>
<data name="Email_Registration_Subject" xml:space="preserve">
<value>{0}: Please verify your email address</value>
</data>
<data name="Front_About" xml:space="preserve">
<value>About</value>
</data>
@ -405,9 +422,6 @@
<data name="Search_Year" xml:space="preserve">
<value>Year</value>
</data>
<data name="Support_AccountValidation" xml:space="preserve">
<value>Every account is validated by an administrator, so be patient and check again later</value>
</data>
<data name="Support_AdditionsToBuildFeed" xml:space="preserve">
<value>Additions to BuildFeed</value>
</data>
@ -426,6 +440,12 @@
<data name="Support_EmailAddress" xml:space="preserve">
<value>Email address</value>
</data>
<data name="Support_EmailValidationContent" xml:space="preserve">
<value>An validation link has been sent to your email address. Please click the link in the email to verify the account.</value>
</data>
<data name="Support_EmailValidationTitle" xml:space="preserve">
<value>Please verify your email address</value>
</data>
<data name="Support_EnterCurrentPassword" xml:space="preserve">
<value>Enter current password</value>
</data>
@ -435,6 +455,21 @@
<data name="Support_EnterPassword" xml:space="preserve">
<value>Enter password</value>
</data>
<data name="Support_Error_ChangingPasswordFail" xml:space="preserve">
<value>An error occurred when attempting to change your password.</value>
</data>
<data name="Support_Error_DuplicateEmail" xml:space="preserve">
<value>A user account with this email address already exists.</value>
</data>
<data name="Support_Error_DuplicateUserName" xml:space="preserve">
<value>A user account with this user name already exists.</value>
</data>
<data name="Support_Error_InvalidPassword" xml:space="preserve">
<value>The password you have entered is invalid.</value>
</data>
<data name="Support_Error_UnknownError" xml:space="preserve">
<value>An unknown error has occurred.</value>
</data>
<data name="Support_HighestVersion" xml:space="preserve">
<value>Highest version</value>
</data>
@ -462,12 +497,21 @@
<data name="Support_RememberMe" xml:space="preserve">
<value>Remember me</value>
</data>
<data name="Support_ThanksRegister" xml:space="preserve">
<value>Thank you for registering</value>
</data>
<data name="Support_UserName" xml:space="preserve">
<value>Username</value>
</data>
<data name="Support_ValidationFailureContent" xml:space="preserve">
<value>Please check the validation link in your email and try again.</value>
</data>
<data name="Support_ValidationFailureTitle" xml:space="preserve">
<value>We were unable to validate your account.</value>
</data>
<data name="Support_ValidationSuccessContent" xml:space="preserve">
<value>You will now be able to log in using your username and password.</value>
</data>
<data name="Support_ValidationSuccessTitle" xml:space="preserve">
<value>You have successfully validated your account.</value>
</data>
<data name="Support_Week" xml:space="preserve">
<value>Week</value>
</data>

View File

@ -46,17 +46,15 @@
<a href="mailto:@mu.Email">@mu.Email</a>
</td>
<td>
@(mu.CreationDate == default(DateTime)
? "Never"
: mu.CreationDate.Humanize())
@mu.CreationDate.Humanize()
</td>
<td>
@(mu.LastLoginDate == default(DateTime)
@(mu.LastLoginDate == DateTime.MinValue
? "Never"
: mu.LastLoginDate.Humanize())
</td>
<td>
@(mu.LastLockoutDate == default(DateTime)
@(mu.LastLockoutDate == DateTime.MinValue
? "Never"
: mu.LastLockoutDate.Humanize())
</td>

View File

@ -73,23 +73,23 @@
<Reference Include="Microsoft.AI.Agent.Intercept, Version=2.0.7.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.Agent.Intercept.2.0.7\lib\net45\Microsoft.AI.Agent.Intercept.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AI.DependencyCollector, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.DependencyCollector.2.2.0\lib\net45\Microsoft.AI.DependencyCollector.dll</HintPath>
<Reference Include="Microsoft.AI.DependencyCollector, Version=2.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.DependencyCollector.2.3.0\lib\net45\Microsoft.AI.DependencyCollector.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AI.PerfCounterCollector, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.PerfCounterCollector.2.2.0\lib\net45\Microsoft.AI.PerfCounterCollector.dll</HintPath>
<Reference Include="Microsoft.AI.PerfCounterCollector, Version=2.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.PerfCounterCollector.2.3.0\lib\net45\Microsoft.AI.PerfCounterCollector.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AI.ServerTelemetryChannel, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.2.2.0\lib\net45\Microsoft.AI.ServerTelemetryChannel.dll</HintPath>
<Reference Include="Microsoft.AI.ServerTelemetryChannel, Version=2.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.2.3.0\lib\net45\Microsoft.AI.ServerTelemetryChannel.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AI.Web, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.Web.2.2.0\lib\net45\Microsoft.AI.Web.dll</HintPath>
<Reference Include="Microsoft.AI.Web, Version=2.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.Web.2.3.0\lib\net45\Microsoft.AI.Web.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AI.WindowsServer, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.WindowsServer.2.2.0\lib\net45\Microsoft.AI.WindowsServer.dll</HintPath>
<Reference Include="Microsoft.AI.WindowsServer, Version=2.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.WindowsServer.2.3.0\lib\net45\Microsoft.AI.WindowsServer.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ApplicationInsights, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.2.2.0\lib\net46\Microsoft.ApplicationInsights.dll</HintPath>
<Reference Include="Microsoft.ApplicationInsights, Version=2.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.2.3.0\lib\net46\Microsoft.ApplicationInsights.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.3\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll</HintPath>
@ -109,10 +109,10 @@
<HintPath>..\packages\MongoDB.Driver.Core.2.4.3\lib\net45\MongoDB.Driver.Core.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.10.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
<HintPath>..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="OneSignal.CSharp.SDK, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\OneSignal.CSharp.SDK.0.9\lib\net45\OneSignal.CSharp.SDK.dll</HintPath>
<HintPath>..\packages\OneSignal.CSharp.SDK.0.10\lib\net45\OneSignal.CSharp.SDK.dll</HintPath>
</Reference>
<Reference Include="RestSharp, Version=105.2.3.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\RestSharp.105.2.3\lib\net46\RestSharp.dll</HintPath>
@ -195,6 +195,7 @@
<Compile Include="Areas\admin\Controllers\metaController.cs" />
<Compile Include="Areas\admin\Models\ViewModel\MetaListing.cs" />
<Compile Include="Code\AiHandleErrorAttribute.cs" />
<Compile Include="Code\Base32Encoding.cs" />
<Compile Include="Code\CustomContentTypeAttribute.cs" />
<Compile Include="Code\DateTimeModelBinder.cs" />
<Compile Include="Code\DisplayHelpers.cs" />
@ -202,11 +203,14 @@
<Compile Include="App_Start\RouteConfig.cs" />
<Compile Include="Areas\admin\adminAreaRegistration.cs" />
<Compile Include="Areas\admin\Controllers\usersController.cs" />
<Compile Include="Code\EmailManager\EmailManager.cs" />
<Compile Include="Code\EmailManager\RegistrationEmail.cs" />
<Compile Include="Code\MvcIntrinsics.cs" />
<Compile Include="Code\OneSignalHelper.cs" />
<Compile Include="Code\Options\Locale.cs" />
<Compile Include="Code\Options\Theme.cs" />
<Compile Include="Code\OutputCachePushAttribute.cs" />
<Compile Include="Controllers\AccountController.cs" />
<Compile Include="Controllers\ApiController.cs" />
<Compile Include="Controllers\BaseController.cs" />
<Compile Include="Controllers\FrontController.cs" />
@ -391,10 +395,10 @@
<Content Include="Views\shared\_default.cshtml" />
<Content Include="Views\front\EditBuild.cshtml" />
<Content Include="Views\shared\DisplayTemplates\Enumeration.cshtml" />
<Content Include="Views\support\register.cshtml" />
<Content Include="Views\support\login.cshtml" />
<Content Include="Views\support\password.cshtml" />
<Content Include="Views\support\thanks_register.cshtml" />
<Content Include="Views\account\register.cshtml" />
<Content Include="Views\account\login.cshtml" />
<Content Include="Views\account\password.cshtml" />
<Content Include="Views\account\registerthanks.cshtml" />
<Content Include="Views\support\rss.cshtml" />
<Content Include="Views\support\sitemap.cshtml" />
<Content Include="smtp.config">
@ -416,6 +420,8 @@
<Content Include="Views\front\AddBulk.cshtml" />
<Content Include="Scripts\trumbowyg\plugins\colors\ui\sass\trumbowyg.colors.scss" />
<Content Include="Scripts\trumbowyg\ui\sass\trumbowyg.scss" />
<Content Include="Views\account\validate-success.cshtml" />
<Content Include="Views\account\validate-failure.cshtml" />
</ItemGroup>
<ItemGroup />
<ItemGroup>

View File

@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=code_005Cemailmanager/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@ -0,0 +1,133 @@
using System;
namespace BuildFeed.Code
{
/* Based off http://stackoverflow.com/a/7135008 */
public class Base32Encoding
{
public static byte[] ToBytes(string input)
{
if (string.IsNullOrEmpty(input))
{
throw new ArgumentNullException(nameof(input));
}
input = input.TrimEnd('='); //remove padding characters
int byteCount = input.Length * 5 / 8; //this must be TRUNCATED
var returnArray = new byte[byteCount];
byte curByte = 0,
bitsRemaining = 8;
int arrayIndex = 0;
foreach (char c in input)
{
int cValue = CharToValue(c);
int mask;
if (bitsRemaining > 5)
{
mask = cValue << (bitsRemaining - 5);
curByte = (byte)(curByte | mask);
bitsRemaining -= 5;
}
else
{
mask = cValue >> (5 - bitsRemaining);
curByte = (byte)(curByte | mask);
returnArray[arrayIndex++] = curByte;
curByte = (byte)(cValue << (3 + bitsRemaining));
bitsRemaining += 3;
}
}
//if we didn't end with a full byte
if (arrayIndex != byteCount)
{
returnArray[arrayIndex] = curByte;
}
return returnArray;
}
public static string ToString(byte[] input)
{
if (input == null || input.Length == 0)
{
throw new ArgumentNullException(nameof(input));
}
int charCount = (int)Math.Ceiling(input.Length / 5d) * 8;
var returnArray = new char[charCount];
byte nextChar = 0,
bitsRemaining = 5;
int arrayIndex = 0;
foreach (byte b in input)
{
nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining)));
returnArray[arrayIndex++] = ValueToChar(nextChar);
if (bitsRemaining < 4)
{
nextChar = (byte)((b >> (3 - bitsRemaining)) & 31);
returnArray[arrayIndex++] = ValueToChar(nextChar);
bitsRemaining += 5;
}
bitsRemaining -= 3;
nextChar = (byte)((b << bitsRemaining) & 31);
}
//if we didn't end with a full char
if (arrayIndex != charCount)
{
returnArray[arrayIndex++] = ValueToChar(nextChar);
while (arrayIndex != charCount)
returnArray[arrayIndex++] = '='; //padding
}
return new string(returnArray);
}
private static int CharToValue(char c)
{
int value = c;
//65-90 == uppercase letters
if (value < 91 && value > 64)
{
return value - 65;
}
//50-55 == numbers 2-7
if (value < 56 && value > 49)
{
return value - 24;
}
//97-122 == lowercase letters
if (value < 123 && value > 96)
{
return value - 97;
}
throw new ArgumentException("Character is not a Base32 character.", nameof(c));
}
private static char ValueToChar(byte b)
{
if (b < 26)
{
return (char)(b + 65);
}
if (b < 32)
{
return (char)(b + 24);
}
throw new ArgumentException("Byte is not a value Base32 value.", nameof(b));
}
}
}

View File

@ -0,0 +1,7 @@
namespace BuildFeed.Code
{
public static partial class EmailManager
{
private const string EMAIL_FROM = "thomas@buildfeed.net";
}
}

View File

@ -0,0 +1,24 @@
using System.Net.Mail;
using System.Threading.Tasks;
using System.Web.Security;
using BuildFeed.Local;
namespace BuildFeed.Code
{
public static partial class EmailManager
{
public static async Task SendRegistrationEmail(MembershipUser mu, string validationLink)
{
using (MailMessage mm = new MailMessage(EMAIL_FROM, mu.Email))
{
mm.Subject = string.Format(VariantTerms.Email_Registration_Subject, InvariantTerms.SiteName);
mm.Body = string.Format(VariantTerms.Email_Registration_Body, InvariantTerms.SiteName, validationLink);
using (SmtpClient sc = new SmtpClient())
{
await sc.SendMailAsync(mm);
}
}
}
}
}

View File

@ -0,0 +1,156 @@
using System;
using System.Net;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using BuildFeed.Code;
using BuildFeed.Local;
using BuildFeed.Model.View;
using MongoAuth;
namespace BuildFeed.Controllers
{
public class AccountController : BaseController
{
[Route("login/")]
public ActionResult Login() => View();
[HttpPost]
[ValidateAntiForgeryToken]
[Route("login/")]
public ActionResult Login(LoginUser ru)
{
if (ModelState.IsValid)
{
bool isAuthenticated = Membership.ValidateUser(ru.UserName, ru.Password);
if (isAuthenticated)
{
int expiryLength = ru.RememberMe
? 129600
: 60;
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(ru.UserName, true, expiryLength);
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie cookieTicket = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket)
{
Expires = DateTime.Now.AddMinutes(expiryLength),
Path = FormsAuthentication.FormsCookiePath
};
Response.Cookies.Add(cookieTicket);
string returnUrl = string.IsNullOrEmpty(Request.QueryString["ReturnUrl"])
? "/"
: Request.QueryString["ReturnUrl"];
return Redirect(returnUrl);
}
}
ViewData["ErrorMessage"] = "The username and password are not valid.";
return View(ru);
}
[Authorize]
[Route("password/")]
public ActionResult Password() => View();
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize]
[Route("password/")]
public ActionResult Password(ChangePassword cp)
{
if (ModelState.IsValid)
{
MembershipUser user = Membership.GetUser();
if (user != null)
{
bool success = user.ChangePassword(cp.OldPassword, cp.NewPassword);
if (success)
{
return Redirect("/");
}
}
}
ViewData["ErrorMessage"] = VariantTerms.Support_Error_ChangingPasswordFail;
return View(cp);
}
[Route("logout/")]
public ActionResult Logout()
{
FormsAuthentication.SignOut();
return Redirect("/");
}
[Route("register/")]
public ActionResult Register() => View();
[HttpPost]
[ValidateAntiForgeryToken]
[Route("register/")]
public async Task<ActionResult> Register(RegistrationUser ru)
{
if (ModelState.IsValid)
{
MembershipCreateStatus status;
MembershipUser mu = Membership.CreateUser(ru.UserName, ru.Password, ru.EmailAddress, "{IGNORE}", "{IGNORE}", false, out status);
switch (status)
{
case MembershipCreateStatus.Success:
{
Roles.AddUserToRole(mu.UserName, "Users");
MongoMembershipProvider provider = (MongoMembershipProvider)Membership.Provider;
Guid id = (Guid)mu.ProviderUserKey;
string hash = (await provider.GenerateValidationHash(id)).ToLower();
string fullUrl = Request.Url?.GetLeftPart(UriPartial.Authority) + Url.Action("Validate",
"Account",
new
{
id,
hash
});
await EmailManager.SendRegistrationEmail(mu, fullUrl);
return RedirectToAction(nameof(RegisterThanks));
}
case MembershipCreateStatus.InvalidPassword:
ViewData["ErrorMessage"] = VariantTerms.Support_Error_InvalidPassword;
break;
case MembershipCreateStatus.DuplicateEmail:
ViewData["ErrorMessage"] = VariantTerms.Support_Error_DuplicateEmail;
break;
case MembershipCreateStatus.DuplicateUserName:
ViewData["ErrorMessage"] = VariantTerms.Support_Error_DuplicateUserName;
break;
default:
ViewData["ErrorMessage"] = VariantTerms.Support_Error_UnknownError;
break;
}
}
return View(ru);
}
[Route("register/thanks/")]
public ActionResult RegisterThanks() => View();
[Route("validate/{id:guid}/{hash}/")]
public async Task<ActionResult> Validate(Guid id, string hash)
{
MongoMembershipProvider provider = (MongoMembershipProvider)Membership.Provider;
bool success = await provider.ValidateUserFromHash(id, hash);
return View(success
? "validate-success"
: "validate-failure");
}
}
}

View File

@ -12,6 +12,7 @@
using BuildFeed.Model.View;
using OneSignal.CSharp.SDK;
#pragma warning disable SG0016 // Controller method is vulnerable to CSRF - Not relevant for API
namespace BuildFeed.Controllers
{
public class ApiController : System.Web.Http.ApiController
@ -70,7 +71,7 @@ public async Task<bool> AddWin10Builds(NewBuildPost apiModel)
{
return false;
}
if (Membership.ValidateUser(apiModel.Username, apiModel.Password))
if (Membership.ValidateUser(apiModel.Username, apiModel.Password) && (Roles.IsUserInRole(apiModel.Username, "Editors") || Roles.IsUserInRole(apiModel.Username, "Administrators")))
{
IEnumerable<Build> builds = apiModel.NewBuilds.Select(nb => new Build
{

View File

@ -378,7 +378,7 @@ public async Task<ActionResult> ViewVersionPage(uint major, uint minor, int page
}
[Route("add/")]
[Authorize]
[Authorize(Roles = "Administrators,Editors")]
public ActionResult AddBuild()
{
Build b = new Build
@ -389,7 +389,8 @@ public ActionResult AddBuild()
}
[Route("add/")]
[Authorize]
[ValidateAntiForgeryToken]
[Authorize(Roles = "Administrators,Editors")]
[HttpPost]
public async Task<ActionResult> AddBuild(Build build)
{
@ -429,14 +430,15 @@ public async Task<ActionResult> AddBuild(Build build)
}
[Route("bulk/")]
[Authorize]
[Authorize(Roles = "Administrators")]
public ActionResult AddBulk()
{
return View();
}
[Route("bulk/")]
[Authorize]
[ValidateAntiForgeryToken]
[Authorize(Roles = "Administrators")]
[HttpPost]
public async Task<ActionResult> AddBulk(FormCollection values)
{
@ -509,7 +511,7 @@ public async Task<ActionResult> AddBulk(FormCollection values)
}
[Route("edit/{id}/")]
[Authorize]
[Authorize(Roles = "Administrators,Editors")]
public async Task<ActionResult> EditBuild(Guid id)
{
Build b = await _bModel.SelectById(id);
@ -517,7 +519,8 @@ public async Task<ActionResult> EditBuild(Guid id)
}
[Route("edit/{id}/")]
[Authorize]
[ValidateAntiForgeryToken]
[Authorize(Roles = "Administrators,Editors")]
[HttpPost]
public async Task<ActionResult> EditBuild(Guid id, Build build)
{

View File

@ -2,10 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;
using System.Xml.Linq;
using BuildFeed.Code;
using BuildFeed.Local;
@ -23,116 +21,6 @@ public SupportController()
_bModel = new BuildRepository();
}
[Route("login/")]
public ActionResult Login() => View();
[HttpPost]
[Route("login/")]
public ActionResult Login(LoginUser ru)
{
if (ModelState.IsValid)
{
bool isAuthenticated = Membership.ValidateUser(ru.UserName, ru.Password);
if (isAuthenticated)
{
int expiryLength = ru.RememberMe
? 129600
: 60;
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(ru.UserName, true, expiryLength);
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie cookieTicket = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket)
{
Expires = DateTime.Now.AddMinutes(expiryLength),
Path = FormsAuthentication.FormsCookiePath
};
Response.Cookies.Add(cookieTicket);
string returnUrl = string.IsNullOrEmpty(Request.QueryString["ReturnUrl"])
? "/"
: Request.QueryString["ReturnUrl"];
return Redirect(returnUrl);
}
}
ViewData["ErrorMessage"] = "The username and password are not valid.";
return View(ru);
}
[Authorize]
[Route("password/")]
public ActionResult Password() => View();
[HttpPost]
[Authorize]
[Route("password/")]
public ActionResult Password(ChangePassword cp)
{
if (ModelState.IsValid)
{
MembershipUser user = Membership.GetUser();
if (user != null)
{
bool success = user.ChangePassword(cp.OldPassword, cp.NewPassword);
if (success)
{
return Redirect("/");
}
}
}
ViewData["ErrorMessage"] = "There was an error changing your password.";
return View(cp);
}
[Route("logout/")]
public ActionResult Logout()
{
FormsAuthentication.SignOut();
return Redirect("/");
}
[Route("register/")]
public ActionResult Register() => View();
[HttpPost]
[Route("register/")]
public ActionResult Register(RegistrationUser ru)
{
if (ModelState.IsValid)
{
MembershipCreateStatus status;
Membership.CreateUser(ru.UserName, ru.Password, ru.EmailAddress, "THIS WILL BE IGNORED", "I WILL BE IGNORED", false, out status);
switch (status)
{
case MembershipCreateStatus.Success:
return RedirectToAction("thanks_register");
case MembershipCreateStatus.InvalidPassword:
ViewData["ErrorMessage"] = "The password is invalid.";
break;
case MembershipCreateStatus.DuplicateEmail:
ViewData["ErrorMessage"] = "A user account with this email address already exists.";
break;
case MembershipCreateStatus.DuplicateUserName:
ViewData["ErrorMessage"] = "A user account with this user name already exists.";
break;
default:
ViewData["ErrorMessage"] = "Unspecified error.";
break;
}
}
return View(ru);
}
[Route("register/thanks/")]
public ActionResult thanks_register() => View();
[Route("rss")]
public async Task<ActionResult> Rss()
{

View File

@ -4,6 +4,7 @@
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;
using BuildFeed.Code;
using BuildFeed.Code.Options;
using BuildFeed.Model;
@ -30,6 +31,10 @@ protected void Application_Start()
ModelBinders.Binders.Add(typeof(DateTime), db);
ModelBinders.Binders.Add(typeof(DateTime?), db);
Roles.CreateRole("Administrators");
Roles.CreateRole("Editors");
Roles.CreateRole("Users");
MongoConfig.SetupIndexes();
}

View File

@ -1,4 +1,5 @@
@model BuildFeed.Model.View.LoginUser
@using BuildFeed.Controllers
@model BuildFeed.Model.View.LoginUser
@{
ViewBag.Title = $"{VariantTerms.Support_Login} | {InvariantTerms.SiteName}";
@ -57,9 +58,9 @@
<div class="form-group">
<div>
<input type="submit" value="@VariantTerms.Support_Login" class="btn btn-primary" /> &ensp;
@Html.ActionLink(VariantTerms.Support_Register, "register", new
@Html.ActionLink(VariantTerms.Support_Register, nameof(AccountController.Register), new
{
controller = "Support"
controller = "Account"
}, new
{
@class = "btn btn-default"

View File

@ -0,0 +1,6 @@
@{
ViewBag.Title = $"{VariantTerms.Support_EmailValidationTitle} | {InvariantTerms.SiteName}";
}
<h1>@VariantTerms.Support_EmailValidationTitle</h1>
<p>@VariantTerms.Support_EmailValidationContent</p>

View File

@ -0,0 +1,6 @@
@{
ViewBag.Title = $"{VariantTerms.Support_ValidationFailureTitle} | {InvariantTerms.SiteName}";
}
<h1>@VariantTerms.Support_ValidationFailureTitle</h1>
<p>@VariantTerms.Support_ValidationFailureContent</p>

View File

@ -0,0 +1,6 @@
@{
ViewBag.Title = $"{VariantTerms.Support_ValidationSuccessTitle} | {InvariantTerms.SiteName}";
}
<h1>@VariantTerms.Support_ValidationSuccessTitle</h1>
<p>@VariantTerms.Support_ValidationSuccessContent</p>

View File

@ -143,7 +143,7 @@
<div class="addthis_sharing_toolbox"></div>
<br />
@if (User.Identity.IsAuthenticated)
@if (Roles.IsUserInRole("Editors") || Roles.IsUserInRole("Administrators"))
{
<h3>@VariantTerms.Front_EditorActions</h3>
<p class="build-details-flex-value">

View File

@ -49,7 +49,7 @@
<i class="fa fa-lock fa-fw"></i> @VariantTerms.Front_Private</span>
</p>
}
@if (User.Identity.IsAuthenticated)
@if (Roles.IsUserInRole("Editors") || Roles.IsUserInRole("Administrators"))
{
<p>
<a href="@Url.Action(nameof(FrontController.EditBuild), new

View File

@ -78,7 +78,7 @@
<i class="fa fa-lock fa-fw"></i> @VariantTerms.Front_Private</span>
</p>
}
@if (User.Identity.IsAuthenticated)
@if (Roles.IsUserInRole("Editors") || Roles.IsUserInRole("Administrators"))
{
<p>
<a href="@Url.Action(nameof(FrontController.EditBuild), new

View File

@ -75,7 +75,7 @@
<i class="fa fa-lock fa-fw"></i> @VariantTerms.Front_Private</span>
</p>
}
@if (User.Identity.IsAuthenticated)
@if (Roles.IsUserInRole("Editors") || Roles.IsUserInRole("Administrators"))
{
<p>
<a href="@Url.Action(nameof(FrontController.EditBuild), new

View File

@ -74,7 +74,7 @@
<i class="fa fa-lock fa-fw"></i> @VariantTerms.Front_Private</span>
</p>
}
@if (User.Identity.IsAuthenticated)
@if (Roles.IsUserInRole("Editors") || Roles.IsUserInRole("Administrators"))
{
<p>
<a href="@Url.Action(nameof(FrontController.EditBuild), new

View File

@ -84,7 +84,7 @@
<i class="fa fa-lock fa-fw"></i> @VariantTerms.Front_Private</span>
</p>
}
@if (User.Identity.IsAuthenticated)
@if (Roles.IsUserInRole("Editors") || Roles.IsUserInRole("Administrators"))
{
<p>
<a href="@Url.Action(nameof(FrontController.EditBuild), new

View File

@ -8,7 +8,7 @@
Response.PushPromise(VirtualPathUtility.ToAbsolute(((Theme)ViewBag.Theme).CssPath));
if (isRtl)
{
Response.PushPromise("/res/css/rtl.css");
Response.PushPromise("/res/css/rtl.css");
}
Response.PushPromise("/res/ts/bfs.js");
@ -18,268 +18,282 @@
? "rtl"
: "ltr")" lang="@CultureInfo.CurrentUICulture.TwoLetterISOLanguageName">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="format-detection" content="telephone=no" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>@ViewBag.Title</title>
<link href="/res/css/default.css" rel="stylesheet" type="text/css" />
<link href="@(VirtualPathUtility.ToAbsolute(((Theme)ViewBag.Theme).CssPath))" rel="stylesheet" type="text/css" />
@if (isRtl)
{
<link href="/res/css/rtl.css" rel="stylesheet" type="text/css" />
}
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,700" rel="stylesheet" type="text/css" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" type="text/css" integrity="sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0=" crossorigin="anonymous" />
<link rel="manifest" href="~/manifest.json">
<link rel="shortcut icon" href="~/favicon.ico" />
<link rel="icon" href="~/favicon.ico" />
<link rel="canonical" href="@Url.Action()" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="@InvariantTerms.SiteName" />
<meta property="og:url" content="@Url.Action()" />
<meta name="theme-color" content="#373736">
<meta name="application-name" content="@InvariantTerms.SiteName" />
<meta name="twitter:site" content="@("@")buildfeed">
@RenderSection("head", false)
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="format-detection" content="telephone=no" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>@ViewBag.Title</title>
<link href="/res/css/default.css" rel="stylesheet" type="text/css" />
<link href="@(VirtualPathUtility.ToAbsolute(((Theme)ViewBag.Theme).CssPath))" rel="stylesheet" type="text/css" />
@if (isRtl)
{
<link href="/res/css/rtl.css" rel="stylesheet" type="text/css" />
}
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,700" rel="stylesheet" type="text/css" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" type="text/css" integrity="sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0=" crossorigin="anonymous" />
<link rel="manifest" href="~/manifest.json">
<link rel="shortcut icon" href="~/favicon.ico" />
<link rel="icon" href="~/favicon.ico" />
<link rel="canonical" href="@Url.Action()" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="@InvariantTerms.SiteName" />
<meta property="og:url" content="@Url.Action()" />
<meta name="theme-color" content="#373736">
<meta name="application-name" content="@InvariantTerms.SiteName" />
<meta name="twitter:site" content="@("@")buildfeed">
@RenderSection("head", false)
<script src="https://cdn.onesignal.com/sdks/OneSignalSDK.js" async></script>
<script>
var OneSignal = window.OneSignal || [];
OneSignal.push(["init", {
appId: "94384f83-dced-4d66-a88c-c2b6e3cdfbaa",
safari_web_id: "web.onesignal.auto.4cc30974-98f9-47ba-8e02-4635d2d477f2",
persistNotification: false,
allowLocalhostAsSecureOrigin: true,
autoRegister: true,
notifyButton: {
enable: false
}
}]);
</script>
<script type="text/javascript">
var appInsights = window.appInsights || function (config) {
function i(config) { t[config] = function () { var i = arguments; t.queue.push(function () { t[config].apply(t, i) }) } } var t = { config: config }, u = document, e = window, o = "script", s = "AuthenticatedUserContext", h = "start", c = "stop", l = "Track", a = l + "Event", v = l + "Page", y = u.createElement(o), r, f; y.src = config.url || "https://az416426.vo.msecnd.net/scripts/a/ai.0.js"; u.getElementsByTagName(o)[0].parentNode.appendChild(y); try { t.cookie = u.cookie } catch (p) { } for (t.queue = [], t.version = "1.0", r = ["Event", "Exception", "Metric", "PageView", "Trace", "Dependency"]; r.length;)i("track" + r.pop()); return i("set" + s), i("clear" + s), i(h + a), i(c + a), i(h + v), i(c + v), i("flush"), config.disableExceptionTracking || (r = "onerror", i("_" + r), f = e[r], e[r] = function (config, i, u, e, o) { var s = f && f(config, i, u, e, o); return s !== !0 && t["_" + r](config, i, u, e, o), s }), t
} ({
instrumentationKey: "4632419f-7a2f-4ab5-8374-34384b650f42"
});
<script src="https://cdn.onesignal.com/sdks/OneSignalSDK.js" async></script>
<script>
var OneSignal = window.OneSignal || [];
OneSignal.push(["init", {
appId: "94384f83-dced-4d66-a88c-c2b6e3cdfbaa",
safari_web_id: "web.onesignal.auto.4cc30974-98f9-47ba-8e02-4635d2d477f2",
persistNotification: false,
allowLocalhostAsSecureOrigin: true,
autoRegister: true,
notifyButton: {
enable: false
}
}]);
</script>
<script type="text/javascript">
var appInsights = window.appInsights || function (config) {
function i(config) { t[config] = function () { var i = arguments; t.queue.push(function () { t[config].apply(t, i) }) } } var t = { config: config }, u = document, e = window, o = "script", s = "AuthenticatedUserContext", h = "start", c = "stop", l = "Track", a = l + "Event", v = l + "Page", y = u.createElement(o), r, f; y.src = config.url || "https://az416426.vo.msecnd.net/scripts/a/ai.0.js"; u.getElementsByTagName(o)[0].parentNode.appendChild(y); try { t.cookie = u.cookie } catch (p) { } for (t.queue = [], t.version = "1.0", r = ["Event", "Exception", "Metric", "PageView", "Trace", "Dependency"]; r.length;)i("track" + r.pop()); return i("set" + s), i("clear" + s), i(h + a), i(c + a), i(h + v), i(c + v), i("flush"), config.disableExceptionTracking || (r = "onerror", i("_" + r), f = e[r], e[r] = function (config, i, u, e, o) { var s = f && f(config, i, u, e, o); return s !== !0 && t["_" + r](config, i, u, e, o), s }), t
}({
instrumentationKey: "4632419f-7a2f-4ab5-8374-34384b650f42"
});
window.appInsights = appInsights;
appInsights.trackPageView();
</script>
window.appInsights = appInsights;
appInsights.trackPageView();
</script>
</head>
<body>
<script>
(function (i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
<script>
(function (i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
(i[r].q = i[r].q || []).push(arguments);
}, i[r].l = 1 * new Date(); a = s.createElement(o),
}, i[r].l = 1 * new Date(); a = s.createElement(o),
m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m);
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
ga('create', 'UA-55417692-1', 'auto');
ga('require', 'displayfeatures');
ga('require', 'linkid', 'linkid.js');
ga('send', 'pageview');
</script>
<header id="page-header">
<div class="container">
<h1>
ga('create', 'UA-55417692-1', 'auto');
ga('require', 'displayfeatures');
ga('require', 'linkid', 'linkid.js');
ga('send', 'pageview');
</script>
<header id="page-header">
<div class="container">
<h1>
@Html.ActionLink(InvariantTerms.SiteName, nameof(FrontController.Index), new
{
controller = "Front",
area = ""
})
</h1>
</div>
</header>
<nav id="page-navigation" role="navigation">
<div class="container">
<button id="page-navigation-toggle">
{
controller = "Front",
area = ""
})
</h1>
</div>
</header>
<nav id="page-navigation" role="navigation">
<div class="container">
<button id="page-navigation-toggle">
<i class="fa fa-bars"></i>&ensp;@VariantTerms.Common_ToggleNavigation
</button>
<ul id="page-navigation-links">
</button>
<ul id="page-navigation-links">
@if (!User.Identity.IsAuthenticated)
{
<li>
<a href="@Url.Action(nameof(SupportController.Login), new
<li>
<a href="@Url.Action(nameof(AccountController.Login), new
{
controller = "Support",
controller = "Account",
area = ""
})" title="@VariantTerms.Common_LogIn">
<i class="fa fa-fw fa-user"></i> @VariantTerms.Common_LogIn
</a>
</li>
<i class="fa fa-fw fa-user"></i> @VariantTerms.Common_LogIn
</a>
</li>
}
else
{
if (Roles.IsUserInRole("Administrators"))
{
<li>
<a href="@Url.Action("index", new
if (Roles.IsUserInRole("Administrators"))
{
<li>
<a href="@Url.Action("index", new
{
controller = "base",
area = "admin"
})" title="@VariantTerms.Common_Admin">
<i class="fa fa-fw fa-cogs"></i> @VariantTerms.Common_Admin
</a>
</li>
}
<li>
<a href="@Url.Action(nameof(FrontController.AddBuild), new
{
<i class="fa fa-fw fa-cogs"></i> @VariantTerms.Common_Admin
</a>
</li>
}
if (Roles.IsUserInRole("Administrators") || Roles.IsUserInRole("Editors"))
{
<li>
<a href="@Url.Action(nameof(FrontController.AddBuild), new
{
controller = "Front",
area = ""
})" title="@VariantTerms.Common_AddBuild">
})" title="@VariantTerms.Common_AddBuild">
<i class="fa fa-fw fa-plus-square"></i> @VariantTerms.Common_AddBuild
</a>
</li>
<li>
<a href="@Url.Action(nameof(FrontController.AddBulk), new
{
</a>
</li>
<li>
<a href="@Url.Action(nameof(FrontController.AddBulk), new
{
controller = "Front",
area = ""
})" title="@VariantTerms.Common_AddBulk">
})" title="@VariantTerms.Common_AddBulk">
<i class="fa fa-fw fa-database"></i> @VariantTerms.Common_AddBulk
</a>
</li>
<li>
<a href="@Url.Action(nameof(SupportController.Logout), new
</a>
</li>
}
<li>
<a href="@Url.Action(nameof(AccountController.Logout), new
{
controller = "Support",
controller = "Account",
area = ""
})" title="@VariantTerms.Common_LogOut">
<i class="fa fa-fw fa-user"></i> @VariantTerms.Common_LogOut
</a>
</li>
<i class="fa fa-fw fa-user"></i> @VariantTerms.Common_LogOut
</a>
</li>
<li>
<a href="@Url.Action(nameof(AccountController.Password), new
{
controller = "Account",
area = ""
})" title="@VariantTerms.Common_ChangePassword">
<i class="fa fa-fw fa-key"></i> @VariantTerms.Common_ChangePassword
</a>
</li>
}
<li>
<a href="#" id="page-navigation-search" title="@VariantTerms.Search_Title">
<i class="fa fa-fw fa-search"></i> @VariantTerms.Search_Title
</a>
<a href="#" id="page-navigation-search" title="@VariantTerms.Search_Title">
<i class="fa fa-fw fa-search"></i> @VariantTerms.Search_Title
</a>
</li>
<li>
<a href="@Url.Action(nameof(SupportController.Rss), new
<a href="@Url.Action(nameof(SupportController.Rss), new
{
controller = "Support",
area = ""
})" title="@VariantTerms.Common_RssFeeds">
<i class="fa fa-fw fa-rss"></i> @VariantTerms.Common_RssFeeds
</a>
<i class="fa fa-fw fa-rss"></i> @VariantTerms.Common_RssFeeds
</a>
</li>
<li>
<a href="https://twitter.com/buildfeed" title="@VariantTerms.Common_Twitter" target="_blank" rel="noopener">
<i class="fa fa-fw fa-twitter"></i> @VariantTerms.Common_Twitter
</a>
<a href="https://twitter.com/buildfeed" title="@VariantTerms.Common_Twitter" target="_blank" rel="noopener">
<i class="fa fa-fw fa-twitter"></i> @VariantTerms.Common_Twitter
</a>
</li>
<li class="dropdown-parent">
<a href="#">
<i class="fa fa-gear"></i>
</a>
<ul class="dropdown-menu">
<li id="settings-theme-menu" class="dropdown-menu-block">
<h4>@VariantTerms.Common_NavigationTheme</h4>
<ul>
@foreach (Theme item in Theme.AvailableThemes)
{
<li>
<a href="#" data-theme="@item.CookieValue">@item.DisplayName</a>
</li>
}
</ul>
</li>
<li id="settings-lang-menu" class="dropdown-menu-block">
<h4>@VariantTerms.Common_NavigationLanguage</h4>
<ul>
@foreach (Locale locale in Locale.AvailableLocales)
{
<li>
<a href="#" data-lang="@locale.LocaleId" dir="@(locale.Info.TextInfo.IsRightToLeft
<a href="#">
<i class="fa fa-gear"></i>
</a>
<ul class="dropdown-menu">
<li id="settings-theme-menu" class="dropdown-menu-block">
<h4>@VariantTerms.Common_NavigationTheme</h4>
<ul>
@foreach (Theme item in Theme.AvailableThemes)
{
<li>
<a href="#" data-theme="@item.CookieValue">@item.DisplayName</a>
</li>
}
</ul>
</li>
<li id="settings-lang-menu" class="dropdown-menu-block">
<h4>@VariantTerms.Common_NavigationLanguage</h4>
<ul>
@foreach (Locale locale in Locale.AvailableLocales)
{
<li>
<a href="#" data-lang="@locale.LocaleId" dir="@(locale.Info.TextInfo.IsRightToLeft
? "rtl"
: "ltr")">
@locale.DisplayName</a>
</li>
}
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</li>
</ul>
@locale.DisplayName
</a>
</li>
}
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
</nav>
<article id="page-content">
<div class="container">
@RenderBody()
</div>
</article>
<footer id="page-footer">
<div class="container">
<div class="footer-flex">
</ul>
</div>
</nav>
<article id="page-content">
<div class="container">
@RenderBody()
</div>
</article>
<footer id="page-footer">
<div class="container">
<div class="footer-flex">
<div class="footer-flex-item">
<p>
<a href="@Url.Action(nameof(SupportController.Sitemap), new
<p>
<a href="@Url.Action(nameof(SupportController.Sitemap), new
{
controller = "Support",
area = ""
})">
@VariantTerms.Common_Sitemap</a>
</p>
<p>
<i class="fa fa-language"></i>&ensp;
@CultureInfo.CurrentUICulture.NativeName
(<a href="@Url.Action(nameof(SupportController.Credits), new
@VariantTerms.Common_Sitemap
</a>
</p>
<p>
<i class="fa fa-language"></i>&ensp;
@CultureInfo.CurrentUICulture.NativeName
(<a href="@Url.Action(nameof(SupportController.Credits), new
{
controller = "Support",
area = ""
})">
@VariantTerms.Common_Credits
</a>)
</p>
@VariantTerms.Common_Credits
</a>)
</p>
</div>
<div class="footer-flex-item">
<p>
&copy; 2013 - @DateTime.Now.Year.ToString(), <span dir="ltr">@InvariantTerms.SiteName</span>
</p>
<p>
@Html.Raw(string.Format(VariantTerms.Common_DevelopedBy, $"<a href=\"https://twitter.com/tomhounsell\" target=\"_blank\" dir=\"ltr\" rel=\"noopener\">{InvariantTerms.DeveloperName}</a>"))
</p>
<p>
@Html.Raw(string.Format(VariantTerms.Common_ContributeOn, "<a href=\"https://github.com/hounsell/BuildFeed\" target=\"_blank\" dir=\"ltr\" rel=\"noopener\"><i class=\"fa fa-github\"></i>&ensp;GitHub</a>"))
</p>
<p>
&copy; 2013 - @DateTime.Now.Year.ToString(), <span dir="ltr">@InvariantTerms.SiteName</span>
</p>
<p>
@Html.Raw(string.Format(VariantTerms.Common_DevelopedBy, $"<a href=\"https://twitter.com/tomhounsell\" target=\"_blank\" dir=\"ltr\" rel=\"noopener\">{InvariantTerms.DeveloperName}</a>"))
</p>
<p>
@Html.Raw(string.Format(VariantTerms.Common_ContributeOn, "<a href=\"https://github.com/hounsell/BuildFeed\" target=\"_blank\" dir=\"ltr\" rel=\"noopener\"><i class=\"fa fa-github\"></i>&ensp;GitHub</a>"))
</p>
</div>
</div>
</div>
</footer>
</div>
</div>
</footer>
<div id="modal-search-overlay">
<div id="modal-search">
<h3>@VariantTerms.Search_BuildFeed</h3>
<div id="modal-search-box">
<div id="modal-search-overlay">
<div id="modal-search">
<h3>@VariantTerms.Search_BuildFeed</h3>
<div id="modal-search-box">
<input id="modal-search-input" type="text" placeholder="@VariantTerms.Search_TypePlaceholder" />
<button id="modal-search-button">
<i class="fa fa-search"></i>
<i class="fa fa-search"></i>
</button>
</div>
<div id="modal-search-result"></div>
</div>
</div>
<div id="menu-open-overlay"></div>
</div>
<div id="modal-search-result"></div>
</div>
</div>
<div id="menu-open-overlay"></div>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jsrender/1.0.0-rc.70/jsrender.min.js" integrity="sha256-3UBtL0tzgKVuJU8ZZiWLXEWGEjXEr6Z023rpauMnBUE=" crossorigin="anonymous"></script>
<script type="text/javascript" src="/res/ts/bfs.js"></script>
@RenderSection("scripts", false)
<script id="result-template" type="text/x-jsrender">
<a href="{{:Url}}" class="search-result-item" title="{{:Title}}">
<h4 class="search-result-heading no-wrapping">{{:Label}}</h4>
<p class="search-result-text">{{:Group}}</p>
</a>
</script>
<script type="text/javascript" src="//s7.addthis.com/js/300/addthis_widget.js#pubid=ra-5431719a661cbfd0" async="async"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jsrender/1.0.0-rc.70/jsrender.min.js" integrity="sha256-3UBtL0tzgKVuJU8ZZiWLXEWGEjXEr6Z023rpauMnBUE=" crossorigin="anonymous"></script>
<script type="text/javascript" src="/res/ts/bfs.js"></script>
@RenderSection("scripts", false)
<script id="result-template" type="text/x-jsrender">
<a href="{{:Url}}" class="search-result-item" title="{{:Title}}">
<h4 class="search-result-heading no-wrapping">{{:Label}}</h4>
<p class="search-result-text">{{:Group}}</p>
</a>
</script>
<script type="text/javascript" src="//s7.addthis.com/js/300/addthis_widget.js#pubid=ra-5431719a661cbfd0" async="async"></script>
</body>
</html>

View File

@ -1,6 +0,0 @@
@{
ViewBag.Title = $"{VariantTerms.Support_ThanksRegister} | {InvariantTerms.SiteName}";
}
<h1>@VariantTerms.Support_ThanksRegister</h1>
<p>@VariantTerms.Support_AccountValidation</p>

View File

@ -47,13 +47,13 @@
<package id="jquery.TypeScript.DefinitelyTyped" version="3.1.2" targetFramework="net462" />
<package id="jQuery.Validation" version="1.16.0" targetFramework="net462" />
<package id="jsrender.TypeScript.DefinitelyTyped" version="0.1.8" targetFramework="net461" />
<package id="Microsoft.ApplicationInsights" version="2.2.0" targetFramework="net462" />
<package id="Microsoft.ApplicationInsights" version="2.3.0" targetFramework="net462" />
<package id="Microsoft.ApplicationInsights.Agent.Intercept" version="2.0.7" targetFramework="net462" />
<package id="Microsoft.ApplicationInsights.DependencyCollector" version="2.2.0" targetFramework="net462" />
<package id="Microsoft.ApplicationInsights.PerfCounterCollector" version="2.2.0" targetFramework="net462" />
<package id="Microsoft.ApplicationInsights.Web" version="2.2.0" targetFramework="net462" />
<package id="Microsoft.ApplicationInsights.WindowsServer" version="2.2.0" targetFramework="net462" />
<package id="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel" version="2.2.0" targetFramework="net462" />
<package id="Microsoft.ApplicationInsights.DependencyCollector" version="2.3.0" targetFramework="net462" />
<package id="Microsoft.ApplicationInsights.PerfCounterCollector" version="2.3.0" targetFramework="net462" />
<package id="Microsoft.ApplicationInsights.Web" version="2.3.0" targetFramework="net462" />
<package id="Microsoft.ApplicationInsights.WindowsServer" version="2.3.0" targetFramework="net462" />
<package id="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel" version="2.3.0" targetFramework="net462" />
<package id="Microsoft.AspNet.Mvc" version="5.2.3" targetFramework="net462" />
<package id="Microsoft.AspNet.Razor" version="3.2.3" targetFramework="net462" />
<package id="Microsoft.AspNet.WebApi" version="5.2.3" targetFramework="net462" />
@ -69,8 +69,8 @@
<package id="MongoDB.Bson" version="2.4.3" targetFramework="net462" />
<package id="MongoDB.Driver" version="2.4.3" targetFramework="net462" />
<package id="MongoDB.Driver.Core" version="2.4.3" targetFramework="net462" />
<package id="Newtonsoft.Json" version="10.0.1" targetFramework="net462" />
<package id="OneSignal.CSharp.SDK" version="0.9" targetFramework="net462" />
<package id="Newtonsoft.Json" version="10.0.2" targetFramework="net462" />
<package id="OneSignal.CSharp.SDK" version="0.10" targetFramework="net462" />
<package id="RestSharp" version="105.2.3" targetFramework="net462" />
<package id="System.Collections" version="4.3.0" targetFramework="net462" />
<package id="System.Linq" version="4.3.0" targetFramework="net462" />

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -87,7 +87,8 @@ table
word-wrap: break-word;
}
.field-validation-error
.field-validation-error,
.text-danger
{
display: block;
margin: #{(1em / 3)} 0;
@ -145,7 +146,7 @@ nav#page-navigation
#page-navigation-links
{
margin: 0 -15px;
margin: 0 -15px #{(1px / 3)}; /* bottom margin fixes Chrome in 4k */
padding: 0;
text-align: right;

View File

@ -1,2 +1,2 @@
"use strict";var BuildFeed;!function(e){function t(e){e.preventDefault();var t=this;t.nextElementSibling.classList.toggle("open")}function n(e){e.preventDefault();var t=this;t.parentElement.classList.toggle("open");var n=document.getElementById("menu-open-overlay");n.classList.add("open")}function a(e){e.preventDefault();for(var t=document.getElementsByClassName("dropdown-parent"),n=0;n<t.length;n++)t[n].classList.remove("open");var a=document.getElementById("menu-open-overlay");a.classList.remove("open")}function o(e){e.preventDefault();var t=this;document.cookie="bf_theme="+t.dataset.theme+"; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/",location.reload(!0)}function r(e){e.preventDefault();var t=this;document.cookie="bf_lang="+t.dataset.lang+"; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/",location.reload(!0)}function d(e){e.preventDefault();var t=document.getElementById("modal-search-overlay");t.classList.add("open")}function l(e){e.preventDefault();var t=document.getElementById("modal-search-overlay");t.classList.remove("open")}function c(e){e.preventDefault(),e.stopPropagation()}function i(e){var t=document.getElementById("modal-search-result");t.innerHTML="","undefined"!=typeof p&&clearTimeout(p),"undefined"!=typeof v&&v.readyState!==XMLHttpRequest.DONE&&v.abort(),p=setInterval(s,200)}function s(){"undefined"!=typeof p&&clearTimeout(p);var e=document.getElementById("modal-search-input");v=new XMLHttpRequest,v.onreadystatechange=u,v.open("GET","/api/GetSearchResult/"+e.value+"/",!0),v.setRequestHeader("accept","application/json"),v.send(null)}function u(e){if(v.readyState===XMLHttpRequest.DONE&&200===v.status){var t=document.getElementById("modal-search-result"),n=document.getElementById("result-template"),a=jsrender.templates(n.innerHTML),o=a.render(JSON.parse(v.responseText));t.innerHTML=o;for(var r=t.getElementsByTagName("a"),d=0;d<r.length;d++)r[d].addEventListener("click",function(e){e.preventDefault();var t=document.getElementById("modal-search-input");ga("send","pageview","/api/GetSearchResult/"+t.value+"/"),location.assign(e.currentTarget.href)})}}function m(e){for(var s=document.getElementsByClassName("dropdown-parent"),u=0;u<s.length;u++)for(var m=0;m<s[u].childNodes.length;m++){var v=s[u].childNodes[m];"A"===v.nodeName&&v.addEventListener("click",n)}var p=document.getElementById("menu-open-overlay");p.addEventListener("click",a);for(var g=document.getElementById("settings-theme-menu").getElementsByTagName("a"),u=0;u<g.length;u++)g[u].addEventListener("click",o);for(var f=document.getElementById("settings-lang-menu").getElementsByTagName("a"),u=0;u<f.length;u++)f[u].addEventListener("click",r);var h=document.getElementById("page-navigation-toggle");h.addEventListener("click",t);var E=document.getElementById("page-navigation-search");E.addEventListener("click",d);var y=document.getElementById("modal-search-overlay");y.addEventListener("click",l);var B=document.getElementById("modal-search");B.addEventListener("click",c);var L=document.getElementById("modal-search-input");L.addEventListener("keyup",i)}var v,p;e.MobileMenuToggle=t,e.DropdownClick=n,e.CloseDropdowns=a,e.SwitchTheme=o,e.SwitchLanguage=r,e.OpenSearch=d,e.CloseSearch=l,e.StopClick=c,e.InitiateSearch=i,e.SendSearch=s,e.CompleteSearch=u,e.BuildFeedSetup=m}(BuildFeed||(BuildFeed={})),window.addEventListener("load",BuildFeed.BuildFeedSetup);
"use strict";var BuildFeed;!function(e){function t(e){e.preventDefault(),this.nextElementSibling.classList.toggle("open")}function n(e){e.preventDefault(),this.parentElement.classList.toggle("open"),document.getElementById("menu-open-overlay").classList.add("open")}function a(e){e.preventDefault();for(var t=document.getElementsByClassName("dropdown-parent"),n=0;n<t.length;n++)t[n].classList.remove("open");document.getElementById("menu-open-overlay").classList.remove("open")}function o(e){e.preventDefault();var t=this;document.cookie="bf_theme="+t.dataset.theme+"; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/",location.reload(!0)}function l(e){e.preventDefault();var t=this;document.cookie="bf_lang="+t.dataset.lang+"; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/",location.reload(!0)}function d(e){e.preventDefault(),document.getElementById("modal-search-overlay").classList.add("open")}function r(e){e.preventDefault(),document.getElementById("modal-search-overlay").classList.remove("open")}function c(e){e.preventDefault(),e.stopPropagation()}function i(e){document.getElementById("modal-search-result").innerHTML="",void 0!==p&&clearTimeout(p),void 0!==g&&g.readyState!==XMLHttpRequest.DONE&&g.abort(),p=setInterval(s,200)}function s(){void 0!==p&&clearTimeout(p);var e=document.getElementById("modal-search-input");g=new XMLHttpRequest,g.onreadystatechange=u,g.open("GET","/api/GetSearchResult/"+e.value+"/",!0),g.setRequestHeader("accept","application/json"),g.send(null)}function u(e){if(g.readyState===XMLHttpRequest.DONE&&200===g.status){var t=document.getElementById("modal-search-result"),n=document.getElementById("result-template"),a=jsrender.templates(n.innerHTML),o=a.render(JSON.parse(g.responseText));t.innerHTML=o;for(var l=t.getElementsByTagName("a"),d=0;d<l.length;d++)l[d].addEventListener("click",function(e){e.preventDefault();var t=document.getElementById("modal-search-input");ga("send","pageview","/api/GetSearchResult/"+t.value+"/"),location.assign(e.currentTarget.href)})}}function m(e){for(var s=document.getElementsByClassName("dropdown-parent"),u=0;u<s.length;u++)for(var m=0;m<s[u].childNodes.length;m++){var g=s[u].childNodes[m];"A"===g.nodeName&&g.addEventListener("click",n)}document.getElementById("menu-open-overlay").addEventListener("click",a);for(var p=document.getElementById("settings-theme-menu").getElementsByTagName("a"),u=0;u<p.length;u++)p[u].addEventListener("click",o);for(var v=document.getElementById("settings-lang-menu").getElementsByTagName("a"),u=0;u<v.length;u++)v[u].addEventListener("click",l);document.getElementById("page-navigation-toggle").addEventListener("click",t),document.getElementById("page-navigation-search").addEventListener("click",d),document.getElementById("modal-search-overlay").addEventListener("click",r),document.getElementById("modal-search").addEventListener("click",c),document.getElementById("modal-search-input").addEventListener("keyup",i)}var g,p;e.MobileMenuToggle=t,e.DropdownClick=n,e.CloseDropdowns=a,e.SwitchTheme=o,e.SwitchLanguage=l,e.OpenSearch=d,e.CloseSearch=r,e.StopClick=c,e.InitiateSearch=i,e.SendSearch=s,e.CompleteSearch=u,e.BuildFeedSetup=m}(BuildFeed||(BuildFeed={})),window.addEventListener("load",BuildFeed.BuildFeedSetup);
//# sourceMappingURL=bfs.js.map

File diff suppressed because one or more lines are too long