Skip to content

Commit

Permalink
Merge pull request #36 from lateflip-io/35-fix-add-mongodb-transactions
Browse files Browse the repository at this point in the history
35 fix add mongodb transactions
  • Loading branch information
ontehfritz authored Nov 6, 2023
2 parents 205352b + e453e02 commit 22fc051
Show file tree
Hide file tree
Showing 12 changed files with 163 additions and 78 deletions.
10 changes: 6 additions & 4 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ jobs:
build:
runs-on: ubuntu-latest
services:
mongodb:
image: mongo:latest
ports:
- 27017:27017
mailhog:
image: mailhog/mailhog:latest
ports:
Expand All @@ -24,6 +20,12 @@ jobs:
uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Start MongoDB
uses: supercharge/[email protected]
with:
mongodb-version: 7
mongodb-replica-set: rs0
mongodb-port: 27017
- name: Install dependencies
run: dotnet restore
- name: Build
Expand Down
2 changes: 2 additions & 0 deletions Bulwark.Auth.sln
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
updateVersion.sh = updateVersion.sh
.releaserc = .releaserc
docker-compose.yaml = docker-compose.yaml
rs-init.sh = rs-init.sh
startdb.sh = startdb.sh
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bulwark.Auth", "src\Bulwark.Auth\Bulwark.Auth.csproj", "{5701FC78-6876-4EB9-8E32-D8EFC12FF9EB}"
Expand Down
16 changes: 12 additions & 4 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
services:
mongodb:
image: "mongo:latest"
image: mongo:7
container_name: mongodb
ports:
- 27017:27017
- '27017:27017'
command: mongod --replSet rs0
healthcheck:
test: |
mongosh --eval "try { rs.status().ok } catch (e) { rs.initiate({ _id: 'rs0', members: [{ _id: 0, host: 'localhost:27017' }] }).ok }"
start_period: 0s
interval: 500ms
timeout: 5s
retries: 5
mailhog:
image: "mailhog/mailhog:latest"
ports:
- 1025:1025
- 8025:8025

- 8025:8025
27 changes: 27 additions & 0 deletions rs-init.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/bash

mongo <<EOF
var config = {
"_id": "dbrs",
"version": 1,
"members": [
{
"_id": 1,
"host": "mongo1:27017",
"priority": 3
},
{
"_id": 2,
"host": "mongo2:27017",
"priority": 2
},
{
"_id": 3,
"host": "mongo3:27017",
"priority": 1
}
]
};
rs.initiate(config, { force: true });
rs.status();
EOF
160 changes: 99 additions & 61 deletions src/Bulwark.Auth.Repositories/MongoDbAccount.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ public class MongoDbAccount : IAccountRepository
private readonly IMongoCollection<VerificationModel>
_verificationCollection;
private readonly IMongoCollection<ForgotModel> _forgotCollection;
private readonly MongoClient _client;

public MongoDbAccount(IMongoDatabase db, IEncrypt encrypt)
public MongoDbAccount(MongoClient client, IMongoDatabase db, IEncrypt encrypt)
{
_client = client;
_accountCollection = db.GetCollection<AccountModel>("account");
_verificationCollection =
db.GetCollection<VerificationModel>("verification");
Expand All @@ -34,36 +36,45 @@ public MongoDbAccount(IMongoDatabase db, IEncrypt encrypt)
/// <exception cref="BulwarkDbException"></exception>
public async Task<VerificationModel> Create(string email, string password)
{
try
using (var session = await _client.StartSessionAsync())
{
var newAccount = new AccountModel
session.StartTransaction();
try
{
Id = ObjectId.GenerateNewId().ToString(),
Email = email,
Password = _encrypt.Encrypt(password),
Salt = Guid.NewGuid().ToString(),
IsVerified = false,
IsEnabled = false,
IsDeleted = false,
Created = DateTime.Now,
Modified = DateTime.Now
};

var verification = new VerificationModel(email,
Guid.NewGuid().ToString());
await _accountCollection.InsertOneAsync(newAccount);
await _verificationCollection.InsertOneAsync(verification);

return verification;
}
catch (MongoWriteException mongoWriteException)
{
if (mongoWriteException.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
throw new BulwarkDbDuplicateException($"{email} already exists");
var newAccount = new AccountModel
{
Id = ObjectId.GenerateNewId().ToString(),
Email = email,
Password = _encrypt.Encrypt(password),
Salt = Guid.NewGuid().ToString(),
IsVerified = false,
IsEnabled = false,
IsDeleted = false,
Created = DateTime.Now,
Modified = DateTime.Now
};

var verification = new VerificationModel(email,
Guid.NewGuid().ToString());
await _accountCollection.InsertOneAsync(newAccount);
await _verificationCollection.InsertOneAsync(verification);
await session.CommitTransactionAsync();
return verification;
}
catch (MongoWriteException mongoWriteException)
{
if (mongoWriteException.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
throw new BulwarkDbDuplicateException($"{email} already exists");
}

throw new BulwarkDbException("Error creating account", mongoWriteException);
throw new BulwarkDbException("Error creating account", mongoWriteException);
}
catch(MongoException mongoException)
{
await session.AbortTransactionAsync();
throw new BulwarkDbException("Error creating account", mongoException);
}
}
}

Expand Down Expand Up @@ -96,7 +107,8 @@ public async Task Verify(string email, string verificationToken)

public async Task<AccountModel> GetAccount(string email)
{
try{
try
{
var account = await _accountCollection
.Find(a => a.Email == email)
.FirstOrDefaultAsync();
Expand All @@ -112,6 +124,7 @@ public async Task<AccountModel> GetAccount(string email)
{
throw new BulwarkDbException($"Error getting account: {email}", e);
}

}

public async Task Delete(string email)
Expand Down Expand Up @@ -174,34 +187,44 @@ public async Task Enable(string email)
/// <exception cref="BulwarkDbDuplicateException"></exception>
public async Task<VerificationModel> ChangeEmail(string email, string newEmail)
{
try
using (var session = await _client.StartSessionAsync())
{
var update = Builders<AccountModel>.Update
session.StartTransaction();
try
{
var update = Builders<AccountModel>.Update
.Set(p => p.Email, newEmail)
.Set(p => p.IsVerified, false)
.Set(p => p.Modified, DateTime.Now);

var verification = new VerificationModel(newEmail,
Guid.NewGuid().ToString());

await _verificationCollection.InsertOneAsync(verification);

var result = await _accountCollection.
UpdateOneAsync(a => a.Email == email, update);

if (result.ModifiedCount != 1)

var verification = new VerificationModel(newEmail,
Guid.NewGuid().ToString());

await _verificationCollection.InsertOneAsync(verification);

var result = await _accountCollection.UpdateOneAsync(a => a.Email == email, update);

if (result.ModifiedCount != 1)
{
throw
new BulwarkDbException($"Email: {email} could not be found");
}
await session.CommitTransactionAsync();
return verification;
}
catch (MongoWriteException exception)
{
throw
new BulwarkDbException($"Email: {email} could not be found");
new BulwarkDbDuplicateException($"Email: {newEmail} in use",
exception);
}
catch (MongoException exception)
{
await session.AbortTransactionAsync();
throw
new BulwarkDbException($"Email: {email} could not be changed",
exception);
}

return verification;
}
catch(MongoWriteException exception)
{
throw
new BulwarkDbDuplicateException($"Email: {newEmail} in use",
exception);
}
}

Expand Down Expand Up @@ -234,21 +257,36 @@ public async Task<ForgotModel> ForgotPassword(string email)
public async Task ResetPasswordWithToken(string email,
string token, string newPassword)
{
var forgotDeleteResult = await _forgotCollection
.DeleteOneAsync(v => v.Email == email && v.Token == token);
using (var session = await _client.StartSessionAsync())
{
try
{
session.StartTransaction();
var forgotDeleteResult = await _forgotCollection
.DeleteOneAsync(v => v.Email == email && v.Token == token);

if (forgotDeleteResult.DeletedCount != 1) throw new BulwarkDbException("Reset token invalid");
var update = Builders<AccountModel>.Update
.Set(p => p.Password, _encrypt.Encrypt(newPassword))
.Set(p => p.Modified, DateTime.Now);
if (forgotDeleteResult.DeletedCount != 1) throw new BulwarkDbException("Reset token invalid");
var update = Builders<AccountModel>.Update
.Set(p => p.Password, _encrypt.Encrypt(newPassword))
.Set(p => p.Modified, DateTime.Now);

var result = await _accountCollection.
UpdateOneAsync(a => a.Email == email, update);
var result = await _accountCollection.UpdateOneAsync(a => a.Email == email, update);

if (result.ModifiedCount != 1)
{
throw
new BulwarkDbException("Password could not be reset");
if (result.ModifiedCount != 1)
{
throw
new BulwarkDbException("Password could not be reset");
}

await session.CommitTransactionAsync();
}
catch (MongoException exception)
{
await session.AbortTransactionAsync();
throw
new BulwarkDbException("Password could not be reset",
exception);
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Bulwark.Auth.TestFixture/MongoDbRandomFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace Bulwark.Auth.TestFixture;

public class MongoDbRandomFixture : IDisposable
{
private MongoClient Client { get; set; }
public MongoClient Client { get; set; }
public IMongoDatabase Db { get; private set; }
private const string _connection = "mongodb://localhost:27017";
private readonly string _testDb;
Expand Down
7 changes: 7 additions & 0 deletions startdb.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash

docker-compose up -d

sleep 5

docker exec mongo1 /scripts/rs-init.sh
3 changes: 2 additions & 1 deletion tests/Bulwark.Auth.Core.Tests/AccountTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ public class AccountTests : IClassFixture<MongoDbRandomFixture>
public AccountTests(MongoDbRandomFixture dbFixture)
{
var encrypt = new BulwarkBCrypt();
var accountRepository = new MongoDbAccount(dbFixture.Db, encrypt);
var accountRepository = new MongoDbAccount(dbFixture.Client,
dbFixture.Db, encrypt);
var signingKeyRepository = new MongoDbSigningKey(dbFixture.Db);
var signingKey = new SigningKey(signingKeyRepository);
var jwtTokenizer = new JwtTokenizer("test", "test", 10, 24,
Expand Down
2 changes: 1 addition & 1 deletion tests/Bulwark.Auth.Core.Tests/AuthenticateTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public AuthenticateTest(MongoDbRandomFixture dbFixture)
{

var encrypt = new BulwarkBCrypt();
var accountRepository = new MongoDbAccount(dbFixture.Db,
var accountRepository = new MongoDbAccount(dbFixture.Client, dbFixture.Db,
encrypt);
var signingKeyRepository = new MongoDbSigningKey(dbFixture.Db);
var signingKey = new SigningKey(signingKeyRepository);
Expand Down
2 changes: 1 addition & 1 deletion tests/Bulwark.Auth.Core.Tests/MagicCodeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public MagicCodeTests(MongoDbRandomFixture dbFixture)
public async void CreateAndAuthenticateMagicCode()
{
var encrypt = new BulwarkBCrypt();
var accountRepository = new MongoDbAccount(_dbFixture.Db,
var accountRepository = new MongoDbAccount(_dbFixture.Client, _dbFixture.Db,
encrypt);
var signingKeyRepository = new MongoDbSigningKey(_dbFixture.Db);
var signingKey = new SigningKey(signingKeyRepository);
Expand Down
8 changes: 4 additions & 4 deletions tests/Bulwark.Auth.Core.Tests/SocialTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,22 @@ public class SocialTests : IClassFixture<MongoDbRandomFixture>

public SocialTests(MongoDbRandomFixture dbFixture)
{
var dbFixture1 = dbFixture;
var encrypt = new BulwarkBCrypt();
IValidatorStrategies validators = new ValidatorStrategies();
IAccountRepository accountRepository = new MongoDbAccount(dbFixture1.Db,
IAccountRepository accountRepository = new MongoDbAccount(dbFixture.Client,
dbFixture.Db,
encrypt);
var signingKeyRepository = new MongoDbSigningKey(dbFixture.Db);
var signingKey = new SigningKey(signingKeyRepository);
var jwtTokenizer = new JwtTokenizer("test", "test", 10, 24,
new List<ISigningAlgorithm> {new Rsa256()}, signingKey);
new MongoDbAuthToken(dbFixture1.Db);
new MongoDbAuthToken(dbFixture.Db);
validators.Add(new MockSocialValidator("bulwark"));
validators.Add(new GoogleValidator(
"651882111548-0hrg7e4o90q1iutmfn02qkf9m90k3d3g.apps.googleusercontent.com"));
validators.Add(new MicrosoftValidator("c9ece416-eadf-4c84-9569-692b8144f50f", "9188040d-6c67-4c5b-b112-36a304b66dad"));
validators.Add(new GithubValidator("lateflip.io" ));
var authorizationRepository = new MongoDbAuthorization(dbFixture1.Db);
var authorizationRepository = new MongoDbAuthorization(dbFixture.Db);
_socialLogin = new SocialLogin(validators, accountRepository,
authorizationRepository, jwtTokenizer);
}
Expand Down
2 changes: 1 addition & 1 deletion tests/Bulwark.Auth.Repository.Tests/MongoDbAccountTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class MongoDbAccountTests : IClassFixture<MongoDbRandomFixture>
public MongoDbAccountTests(MongoDbRandomFixture dbFixture)
{
var encryption = new BulwarkBCrypt();
_accountRepository = new MongoDbAccount(dbFixture.Db,
_accountRepository = new MongoDbAccount(dbFixture.Client, dbFixture.Db,
encryption);
}

Expand Down

0 comments on commit 22fc051

Please sign in to comment.