Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
4025f41
Add JWT authentication
Seme30 Mar 16, 2026
6b76aeb
Rollback web port to 3000
Seme30 Mar 24, 2026
f9dd017
Require JWT config via environment
Seme30 Mar 24, 2026
68bdc05
fix: open redirect replaced with known redirects and JWT secret fall…
Seme30 Mar 25, 2026
8f025f8
fix: Sonar security issues: remove hardcoded URLs, prevent open redir…
Seme30 Mar 25, 2026
27972a7
fix: sonar security and complexity issues, add CI test defaults
Seme30 Mar 25, 2026
5b80dfa
fix: disabled false flags
Seme30 Mar 25, 2026
ff2577f
fix: add using Atlas_Web; to Program.cs
Seme30 Mar 25, 2026
d3f1034
Add JWT config to CI workflows and suppress Sonar false positives
Seme30 Mar 25, 2026
5d656c1
Merge pull request #683 from atlas-bi/feature/jwt-auth
christopherpickering Mar 26, 2026
4f85cf6
Add report API parity endpoints
Seme30 Apr 1, 2026
6e52d32
add :3001 to allowed origins
Seme30 Apr 1, 2026
98b47ce
Merge pull request #685 from atlas-bi/feature/reports-api
christopherpickering Apr 2, 2026
fef26b8
feat(api): extend /me endpoint with roles, permissions and adminEnabled
Seme30 Apr 7, 2026
b955b8b
fix(ci): bump lighthouse workflow to node 18
Seme30 Apr 7, 2026
d5c570e
Merge pull request #686 from atlas-bi/feature/saml-jwt-auth
christopherpickering Apr 8, 2026
43aa1a7
chore(deps) Update all non-major dependencies (#687)
renovate[bot] Apr 12, 2026
c5c1166
chore(deps) Update dependency @rollup/plugin-babel to v7 (#688)
renovate[bot] Apr 12, 2026
fd0f655
feat(reports): add report api with feature flags and permission gates
Seme30 Apr 15, 2026
2ba2925
Merge pull request #690 from atlas-bi/feat/reports-api
christopherpickering Apr 15, 2026
f8c5d4f
feat: add SearchApiController with Solr-backed full-text search
May 4, 2026
fb3b6cc
fix: remove dead constant and add missing lookup types
May 4, 2026
6868c0a
fix(sonar): regex
May 4, 2026
dfd81e7
fi(ci): coverage by switching tests to dotnet collector
May 5, 2026
7e6bab0
Merge pull request #691 from atlas-bi/feat/search-api
christopherpickering May 5, 2026
7d9da06
feat(collections): add collections api
May 6, 2026
a35a17b
Merge pull request #692 from atlas-bi/feat/collections-api
christopherpickering May 7, 2026
2fdb6d5
feat(api): add interactions and profile API endpoints
May 7, 2026
d05249e
Merge pull request #693 from atlas-bi/feat/shared-interaction
christopherpickering May 7, 2026
e92f679
ref: profile API query handling and resolve Sonar issues in profile/i…
May 7, 2026
3f46d81
ref: Rename ProfileQueryOptions.ReportType to ReportTypes to fix Coda…
May 8, 2026
5a44693
Merge pull request #694 from atlas-bi/feat/shared-interaction
christopherpickering May 9, 2026
ee2e6de
chore(deps) Update dependency BrowserStackLocal to v3
renovate[bot] May 9, 2026
293e856
chore(deps) Update dependency coverlet.collector to v8
renovate[bot] May 9, 2026
c31dcfb
chore(deps) Update dependency node to v22
renovate[bot] May 9, 2026
322138b
chore(deps) Update dependency Slugify.Core to v5
renovate[bot] May 9, 2026
1f165be
Merge pull request #657 from atlas-bi/renovate/node-22.x
christopherpickering May 12, 2026
a162ef6
Merge pull request #681 from atlas-bi/renovate/coverlet.collector-8.x
christopherpickering May 12, 2026
ae16b94
Merge pull request #662 from atlas-bi/renovate/slugify.core-5.x
christopherpickering May 12, 2026
7ddb6fe
Merge pull request #648 from atlas-bi/renovate/browserstacklocal-3.x
christopherpickering May 12, 2026
49b59e0
chore: add demo admin local auth setup
Seme30 May 19, 2026
456c491
fix: restrict content security policy frame ancestors
Seme30 May 19, 2026
91ebdd0
Merge pull request #706 from atlas-bi/chore/demo-admin-local-auth
christopherpickering May 21, 2026
17ac526
Add users API with workspace and Razor parity support
Seme30 May 21, 2026
e889b0f
fix: address Codacy findings in users API services
Seme30 May 21, 2026
f90cdbd
Merge pull request #707 from atlas-bi/feat/users-api
christopherpickering May 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .github/workflows/lighthouse.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: setup node
uses: actions/setup-node@v6
with:
node-version: '16.x'
node-version: '22.x'
- name: install node deps
run: npm install
- name: node build
Expand All @@ -38,10 +38,19 @@ jobs:
install: localdb
- name: migrate
run: .\.dotnet-tools\dotnet-ef database update --project web/web.csproj
env:
Jwt__Key: "lighthouse-ci-test-jwt-key-minimum-32-characters-required"
Jwt__Issuer: "atlas-lighthouse-ci"
Jwt__Audience: "atlas-lighthouse-ci"
- name: run Lighthouse CI
run: |
npm install -g @lhci/cli@0.9.x
lhci autorun
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
LHCI_TOKEN: ${{ secrets.LHCI_TOKEN }}
Jwt__Key: "lighthouse-ci-test-jwt-key-minimum-32-characters-required"
Jwt__Issuer: "atlas-lighthouse-ci"
Jwt__Audience: "atlas-lighthouse-ci"
Cors__AllowedOrigins__0: "http://localhost:3000"
Auth__DefaultCallbackPath: "/auth/callback"
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: setup node
uses: actions/setup-node@v6
with:
node-version: '20.x'
node-version: '22.x'
- name: install node deps
run: npm install --ignore-scripts --no-audit --no-fund
- name: Semantic Release
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/sonar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
Jwt__Key: "sonar-ci-test-jwt-key-minimum-32-characters-required-for-build"
Jwt__Issuer: "atlas-sonar-ci"
Jwt__Audience: "atlas-sonar-ci"
Cors__AllowedOrigins__0: "http://localhost:3000"
Auth__DefaultCallbackPath: "/auth/callback"
shell: powershell
run: |
.\.sonar\scanner\dotnet-sonarscanner begin /k:"atlas-bi_atlas-bi-library" /o:"atlas-bi" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io"
Expand Down
40 changes: 34 additions & 6 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: setup node
uses: actions/setup-node@v6
with:
node-version: '18.x'
node-version: '22.x'

- name: install node deps
run: npm install --ignore-scripts --no-audit --no-fund
Expand All @@ -41,7 +41,7 @@ jobs:
- name: setup node
uses: actions/setup-node@v6
with:
node-version: '18.x'
node-version: '22.x'
- name: install node deps
run: npm install --ignore-scripts --no-audit --no-fund
- name: node build
Expand Down Expand Up @@ -82,7 +82,7 @@ jobs:
- name: setup node
uses: actions/setup-node@v6
with:
node-version: '18.x'
node-version: '22.x'
- name: setup java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654
with:
Expand All @@ -94,7 +94,6 @@ jobs:
run: npm run build
- name: install dotnet deps
run: |
dotnet tool install -g coverlet.console
dotnet tool install -g dotnet-reportgenerator-globaltool
dotnet restore
- name: build
Expand All @@ -109,6 +108,21 @@ jobs:
BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}

- name: collect coverage file
if: always()
shell: pwsh
run: |
$coverageFile = Get-ChildItem -Path TestResults -Recurse -Filter coverage.cobertura.xml |
Sort-Object LastWriteTimeUtc -Descending |
Select-Object -First 1

if ($coverageFile) {
Copy-Item $coverageFile.FullName coverage.cobertura.xml -Force
Write-Host "Copied coverage file from $($coverageFile.FullName)"
} else {
Write-Host "Coverage file not found under TestResults"
}

- name: console coverage
if: always()
run: |
Expand Down Expand Up @@ -204,7 +218,7 @@ jobs:
- name: setup node
uses: actions/setup-node@v6
with:
node-version: '18.x'
node-version: '22.x'
- name: setup java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654
with:
Expand All @@ -216,7 +230,6 @@ jobs:
run: npm run build
- name: install dotnet deps
run: |
dotnet tool install -g coverlet.console
dotnet tool install -g dotnet-reportgenerator-globaltool
dotnet restore
- name: build
Expand All @@ -231,6 +244,21 @@ jobs:
BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}

- name: collect coverage file
if: always()
shell: pwsh
run: |
$coverageFile = Get-ChildItem -Path TestResults -Recurse -Filter coverage.cobertura.xml |
Sort-Object LastWriteTimeUtc -Descending |
Select-Object -First 1

if ($coverageFile) {
Copy-Item $coverageFile.FullName coverage.cobertura.xml -Force
Write-Host "Copied coverage file from $($coverageFile.FullName)"
} else {
Write-Host "Coverage file not found under TestResults"
}

- name: console coverage
if: always()
run: |
Expand Down
4 changes: 4 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ services:
environment:
PORT: ${WEB_PORT:-3000}
SEED_DEMO: ${SEED_DEMO:-true}
DEMO_ADMIN_USERNAME: ${DEMO_ADMIN_USERNAME:-}
Jwt__Key: ${JWT_KEY}
Jwt__Issuer: ${JWT_ISSUER}
Jwt__Audience: ${JWT_AUDIENCE}
expose:
- ${WEB_PORT:-3000}
ports:
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"@fontsource/rasa": "^5.0.0",
"@fontsource/source-code-pro": "^5.0.0",
"@fortawesome/fontawesome-free": "^7.0.0",
"@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-babel": "^7.0.0",
"@rollup/plugin-commonjs": "^29.0.0",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-multi-entry": "^7.0.0",
Expand Down Expand Up @@ -86,9 +86,9 @@
"db:update": "dotnet ef database update --project web/web.csproj -v",
"dotnet:publish": "npm run build && dotnet publish web/web.csproj -r win-x86 --self-contained false -c Release -o out",
"test:report_html": "reportgenerator -reports:coverage.cobertura.xml -targetdir:coverage/ -reporttypes:html",
"test:integrationTests": "coverlet web.Tests/bin/Debug/net9.0/web.Tests.dll --target \"dotnet\" --targetargs \"test --filter IntegrationTests --no-build -e Demo=True\" --format cobertura --exclude-by-file \"**/Migrations/*\"",
"test:browserTests": "coverlet web.Tests/bin/Debug/net9.0/web.Tests.dll --target \"dotnet\" --targetargs \"test --filter=BrowserTests --no-build -e Demo=True\" --format cobertura --exclude-by-file \"**/Migrations/*\"",
"test:functionTests": "coverlet web.Tests/bin/Debug/net9.0/web.Tests.dll --target \"dotnet\" --targetargs \"test --filter=FunctionTests --no-build -e Demo=True\" --format cobertura --exclude-by-file \"**/Migrations/*\"",
"test:integrationTests": "dotnet test web.Tests/web.Tests.csproj --filter IntegrationTests --no-build -e Demo=True --collect:\"XPlat Code Coverage;Format=cobertura\" --results-directory TestResults",
"test:browserTests": "dotnet test web.Tests/web.Tests.csproj --filter BrowserTests --no-build -e Demo=True --collect:\"XPlat Code Coverage;Format=cobertura\" --results-directory TestResults",
"test:functionTests": "dotnet test web.Tests/web.Tests.csproj --filter FunctionTests --no-build -e Demo=True --collect:\"XPlat Code Coverage;Format=cobertura\" --results-directory TestResults",
"lint:js": "xo web/wwwroot/js",
"lint:scss": "stylelint \"web/wwwroot/css/**/*.scss\"",
"lint": "npm run lint:js & npm run lint:scss",
Expand Down Expand Up @@ -164,7 +164,7 @@
"unicorn/prefer-at": "warn"
}
},
"version": "3.15.38",
"version": "3.15.39",
"dependencies": {
"sass": "^1.63.6"
}
Expand Down
45 changes: 45 additions & 0 deletions web.Tests/FunctionTests/Authorization/DemoAuthHandler.Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Linq;
using System.Threading.Tasks;
using Atlas_Web.Authentication;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;

namespace web.Tests.FunctionTests.Authorization;

public class DemoAuthHandlerTests
{
[Fact]
public async Task AuthenticateAsync_UsesConfiguredDemoAdminUsername()
{
var options = new DemoSchemeOptions { Username = "local-admin" };
var optionsMonitor = new Mock<IOptionsMonitor<DemoSchemeOptions>>();
optionsMonitor.Setup(x => x.CurrentValue).Returns(options);
optionsMonitor.Setup(x => x.Get(It.IsAny<string>())).Returns(options);

var handler = new DemoAuthHandler(
optionsMonitor.Object,
NullLoggerFactory.Instance,
UrlEncoder.Default,
new SystemClock()

Check warning on line 29 in web.Tests/FunctionTests/Authorization/DemoAuthHandler.Tests.cs

View workflow job for this annotation

GitHub Actions / function tests

'SystemClock' is obsolete: 'Use TimeProvider instead.'

Check warning on line 29 in web.Tests/FunctionTests/Authorization/DemoAuthHandler.Tests.cs

View workflow job for this annotation

GitHub Actions / function tests

'SystemClock' is obsolete: 'Use TimeProvider instead.'

Check warning on line 29 in web.Tests/FunctionTests/Authorization/DemoAuthHandler.Tests.cs

View workflow job for this annotation

GitHub Actions / function tests

'SystemClock' is obsolete: 'Use TimeProvider instead.'

Check warning on line 29 in web.Tests/FunctionTests/Authorization/DemoAuthHandler.Tests.cs

View workflow job for this annotation

GitHub Actions / function tests

'SystemClock' is obsolete: 'Use TimeProvider instead.'

Check warning on line 29 in web.Tests/FunctionTests/Authorization/DemoAuthHandler.Tests.cs

View workflow job for this annotation

GitHub Actions / integration tests

'SystemClock' is obsolete: 'Use TimeProvider instead.'

Check warning on line 29 in web.Tests/FunctionTests/Authorization/DemoAuthHandler.Tests.cs

View workflow job for this annotation

GitHub Actions / integration tests

'SystemClock' is obsolete: 'Use TimeProvider instead.'

Check warning on line 29 in web.Tests/FunctionTests/Authorization/DemoAuthHandler.Tests.cs

View workflow job for this annotation

GitHub Actions / integration tests

'SystemClock' is obsolete: 'Use TimeProvider instead.'

Check warning on line 29 in web.Tests/FunctionTests/Authorization/DemoAuthHandler.Tests.cs

View workflow job for this annotation

GitHub Actions / integration tests

'SystemClock' is obsolete: 'Use TimeProvider instead.'
);

await handler.InitializeAsync(
new AuthenticationScheme("Demo", "Demo", typeof(DemoAuthHandler)),
new DefaultHttpContext()
);

var result = await handler.AuthenticateAsync();

Assert.True(result.Succeeded);
Assert.Equal(
"local-admin",
result.Principal?.Claims.Single(c => c.Type == ClaimTypes.Name).Value
);
}
}
72 changes: 72 additions & 0 deletions web.Tests/FunctionTests/Controllers/AuthApiController.Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Threading.Tasks;
using Atlas_Web.Controllers.Api;
using Atlas_Web.Models;
using Atlas_Web.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using Xunit;

namespace web.Tests.FunctionTests.Controllers;

public class AuthApiControllerTests
{
[Fact]
public async Task Login_UsesConfiguredDemoAdminUsername_WhenDemoModeIsEnabled()
{
var options = new DbContextOptionsBuilder<Atlas_WebContext>()
.UseInMemoryDatabase(databaseName: "auth-api-demo-admin")
.Options;

await using var context = new Atlas_WebContext(options);
context.Users.Add(
new User
{
UserId = 99,
Username = "local-admin",
FullnameCalc = "Local Admin",
}
);
await context.SaveChangesAsync();

var config = new ConfigurationBuilder()
.AddInMemoryCollection(
new Dictionary<string, string>
{
["Demo"] = "True",
["DEMO_ADMIN_USERNAME"] = "local-admin",
["Cors:AllowedOrigins:0"] = "http://localhost:3000",
["Auth:DefaultCallbackPath"] = "/auth/callback",
["Jwt:Issuer"] = "atlas-test-issuer",
["Jwt:Audience"] = "atlas-test-audience",
}
)
.Build();

var signingKey = new SymmetricSecurityKey(
System.Text.Encoding.UTF8.GetBytes(
"test-jwt-secret-key-for-function-tests-32-chars-minimum"
)
);
var jwt = new JwtTokenService(signingKey, "atlas-test-issuer", "atlas-test-audience");
var controller = new AuthApiController(jwt, context, config);

var result = await controller.Login("http://localhost:3000/auth/callback");

var redirect = Assert.IsType<RedirectResult>(result);
var target = new System.Uri(redirect.Url!, System.UriKind.Absolute);
var token = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(target.Query)["token"]
.Single();
var jwtToken = new JwtSecurityTokenHandler().ReadJwtToken(token);

Assert.Equal(
"local-admin",
jwtToken.Claims.Single(c => c.Type == System.Security.Claims.ClaimTypes.Name).Value
);
Assert.Equal("99", jwtToken.Claims.Single(c => c.Type == "UserId").Value);
}
}
Loading
Loading