feat: security and auditing

This commit is contained in:
2026-02-04 16:48:08 -08:00
parent 11be34cd21
commit 0a654f423a
42 changed files with 2195 additions and 160 deletions
@@ -10,6 +10,7 @@ import { FormsModule } from '@angular/forms';
import { ArtService } from '../../services/art.service';
import { AuthService } from '../../services/auth.service';
import { CommentsService } from '../../services/comments.service';
import { SanitizeService } from '../../services/sanitize.service';
import { Art, CreateArtDto, UpdateArtDto, Comment } from '@library/shared-types';
@Component({
@@ -210,15 +211,21 @@ import { Art, CreateArtDto, UpdateArtDto, Comment } from '@library/shared-types'
@if (expandedComments()[art.id]) {
<div class="comments-container">
@if (authService.isAuthenticated()) {
<form (ngSubmit)="addComment(art.id)" class="comment-form">
<textarea
[(ngModel)]="newCommentContent[art.id]"
name="comment"
placeholder="Add a comment (Markdown supported)..."
rows="2"
></textarea>
<button type="submit" class="btn btn-primary btn-sm">Post Comment</button>
</form>
@if (authService.user()?.isBanned) {
<div class="banned-notice">
You have been banned from commenting.
</div>
} @else {
<form (ngSubmit)="addComment(art.id)" class="comment-form">
<textarea
[(ngModel)]="newCommentContent[art.id]"
name="comment"
placeholder="Add a comment (Markdown supported)..."
rows="2"
></textarea>
<button type="submit" class="btn btn-primary btn-sm">Post Comment</button>
</form>
}
}
@if (commentsLoading()[art.id]) {
@@ -236,7 +243,7 @@ import { Art, CreateArtDto, UpdateArtDto, Comment } from '@library/shared-types'
<button (click)="deleteComment(art.id, comment.id)" class="btn btn-danger btn-xs">Delete</button>
}
</div>
<div class="comment-content" [innerHTML]="comment.content"></div>
<div class="comment-content" [innerHTML]="sanitizeService.sanitizeHtml(comment.content)"></div>
</div>
} @empty {
<div class="no-comments">No comments yet. Be the first to comment!</div>
@@ -621,12 +628,24 @@ import { Art, CreateArtDto, UpdateArtDto, Comment } from '@library/shared-types'
color: var(--witch-mauve);
font-size: 0.9rem;
}
.banned-notice {
background: #fef2f2;
border: 1px solid #fecaca;
color: #991b1b;
padding: 0.75rem 1rem;
border-radius: 4px;
text-align: center;
margin-bottom: 1rem;
font-size: 0.9rem;
}
`]
})
export class ArtGalleryComponent implements OnInit {
artService = inject(ArtService);
authService = inject(AuthService);
commentsService = inject(CommentsService);
sanitizeService = inject(SanitizeService);
artPieces = signal<Art[]>([]);
loading = signal(true);