mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 22:26:16 -08:00
fix(settings): harden the parental lock after review
- The M3U group dialog opens only after the PIN, like the Xtream and Stalker dialogs: it lists locked group names and can rewrite the locks. - Change PIN and Disable always verify the stored hash, even while the session is unlocked, so an app left unlocked cannot lose its lock. - Stalker paging judges progress on the raw portal page: withheld ids the list has not seen count as progress, a page made only of locked rows requests the next one itself, and the VOD total is reduced by withheld ids so the grid stops asking once every visible row is in. - Parental lock contract linked from the agent context map after the guidance reorganization; bridge helpers split out to stay under the file-size cap. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
ae5d3f49de
commit
299a162416
32 files changed
+414
-89
No files matched your search
+19
-2
@@ -5,6 +5,7 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { of } from 'rxjs';
|
||||
import { ParentalLockService } from '@iptvnator/services';
|
||||
import { Channel } from '@iptvnator/shared/interfaces';
|
||||
import { ChannelDetailsDialogComponent } from '../channel-details-dialog/channel-details-dialog.component';
|
||||
import { GroupManagementDialogComponent } from './group-management-dialog/group-management-dialog.component';
|
||||
@@ -46,6 +47,7 @@ describe('GroupsViewComponent', () => {
|
||||
let fixture: ComponentFixture<GroupsViewComponent>;
|
||||
let component: GroupsViewComponent;
|
||||
let dialog: { open: jest.Mock };
|
||||
let parentalLock: { requestUnlock: jest.Mock };
|
||||
|
||||
const sportsCenter = createChannel(
|
||||
'sports-1',
|
||||
@@ -102,6 +104,8 @@ describe('GroupsViewComponent', () => {
|
||||
beforeEach(async () => {
|
||||
localStorage.removeItem(GROUP_CHANNEL_SORT_STORAGE_KEY);
|
||||
|
||||
parentalLock = { requestUnlock: jest.fn().mockResolvedValue(true) };
|
||||
|
||||
dialog = {
|
||||
open: jest.fn(),
|
||||
};
|
||||
@@ -117,6 +121,10 @@ describe('GroupsViewComponent', () => {
|
||||
provide: MatDialog,
|
||||
useValue: dialog,
|
||||
},
|
||||
{
|
||||
provide: ParentalLockService,
|
||||
useValue: parentalLock,
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
@@ -498,14 +506,14 @@ describe('GroupsViewComponent', () => {
|
||||
).toEqual(['Movie Classic']);
|
||||
});
|
||||
|
||||
it('opens the manage-groups dialog with all groups and emits updated hidden titles on save', () => {
|
||||
it('opens the manage-groups dialog with all groups and emits updated hidden titles on save', async () => {
|
||||
const hiddenGroupTitlesChanged = jest.fn();
|
||||
component.hiddenGroupTitlesChanged.subscribe(hiddenGroupTitlesChanged);
|
||||
dialog.open.mockReturnValue({
|
||||
afterClosed: () => of({ hiddenGroupTitles: ['News', 'Sports'] }),
|
||||
});
|
||||
|
||||
component.openGroupManagement();
|
||||
await component.openGroupManagement();
|
||||
|
||||
expect(dialog.open).toHaveBeenCalledWith(
|
||||
GroupManagementDialogComponent,
|
||||
@@ -529,6 +537,15 @@ describe('GroupsViewComponent', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('keeps the manage-groups dialog closed when the parental PIN is refused', async () => {
|
||||
// The dialog names every locked group and can rewrite the locks.
|
||||
parentalLock.requestUnlock.mockResolvedValueOnce(false);
|
||||
|
||||
await component.openGroupManagement();
|
||||
|
||||
expect(dialog.open).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('drops the selected-group header in compact mode but keeps the groups rail header', () => {
|
||||
fixture.componentRef.setInput('showHeader', false);
|
||||
fixture.detectChanges();
|
||||
|
||||
+9
-2
@@ -22,7 +22,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { EpgRuntimeBridgeService } from '@iptvnator/epg/data-access';
|
||||
import { resolveChannelEpgLookupKey } from '@iptvnator/m3u-state';
|
||||
import { SettingsStore } from '@iptvnator/services';
|
||||
import { ParentalLockService, SettingsStore } from '@iptvnator/services';
|
||||
import {
|
||||
foldSearchText,
|
||||
Channel,
|
||||
@@ -84,6 +84,7 @@ interface FilteredGroupView {
|
||||
})
|
||||
export class GroupsViewComponent {
|
||||
private readonly dialog = inject(MatDialog);
|
||||
private readonly parentalLock = inject(ParentalLockService);
|
||||
private readonly epgBridge = inject(EpgRuntimeBridgeService);
|
||||
private readonly settingsStore = inject(SettingsStore);
|
||||
readonly supportsEpgMapping = this.epgBridge.supportsEpgMapping;
|
||||
@@ -488,7 +489,13 @@ export class GroupsViewComponent {
|
||||
this.localGroupSearchTerm.set(value);
|
||||
}
|
||||
|
||||
openGroupManagement(): void {
|
||||
async openGroupManagement(): Promise<void> {
|
||||
// The dialog lists every group by name, locked ones included, and can
|
||||
// rewrite the locks — so it sits behind the PIN like the portal
|
||||
// dialogs do.
|
||||
if (!(await this.parentalLock.requestUnlock())) {
|
||||
return;
|
||||
}
|
||||
const groups =
|
||||
this.managementGroups() ??
|
||||
this.allGroups().map<GroupManagementDialogGroup>(
|
||||
|
||||
Reference in new issue
Block a user