feat: implement account settings and API tokens management
- Added SettingsComponent for user profile management, including display name and password change functionality. - Introduced TokensComponent for managing API tokens, including creation and revocation. - Created LayoutComponent for consistent application layout with navigation and user information. - Established main application structure in index.html and main.ts. - Integrated Tailwind CSS for styling and responsive design. - Configured TypeScript settings for strict type checking and module resolution.
This commit is contained in:
167
ts/models/apitoken.ts
Normal file
167
ts/models/apitoken.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* ApiToken model for Stack.Gallery Registry
|
||||
*/
|
||||
|
||||
import * as plugins from '../plugins.ts';
|
||||
import type { IApiToken, ITokenScope, TRegistryProtocol } from '../interfaces/auth.interfaces.ts';
|
||||
import { getDb } from './db.ts';
|
||||
|
||||
@plugins.smartdata.Collection(() => getDb())
|
||||
export class ApiToken
|
||||
extends plugins.smartdata.SmartDataDbDoc<ApiToken, ApiToken>
|
||||
implements IApiToken
|
||||
{
|
||||
@plugins.smartdata.unI()
|
||||
public id: string = '';
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
@plugins.smartdata.index()
|
||||
public userId: string = '';
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public name: string = '';
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
@plugins.smartdata.index({ unique: true })
|
||||
public tokenHash: string = '';
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public tokenPrefix: string = '';
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public protocols: TRegistryProtocol[] = [];
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public scopes: ITokenScope[] = [];
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
@plugins.smartdata.index()
|
||||
public expiresAt?: Date;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public lastUsedAt?: Date;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public lastUsedIp?: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public usageCount: number = 0;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
@plugins.smartdata.index()
|
||||
public isRevoked: boolean = false;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public revokedAt?: Date;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public revokedReason?: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
@plugins.smartdata.index()
|
||||
public createdAt: Date = new Date();
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public createdIp?: string;
|
||||
|
||||
/**
|
||||
* Find token by hash
|
||||
*/
|
||||
public static async findByHash(tokenHash: string): Promise<ApiToken | null> {
|
||||
return await ApiToken.getInstance({
|
||||
tokenHash,
|
||||
isRevoked: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find token by prefix (for listing)
|
||||
*/
|
||||
public static async findByPrefix(tokenPrefix: string): Promise<ApiToken | null> {
|
||||
return await ApiToken.getInstance({
|
||||
tokenPrefix,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all tokens for a user
|
||||
*/
|
||||
public static async getUserTokens(userId: string): Promise<ApiToken[]> {
|
||||
return await ApiToken.getInstances({
|
||||
userId,
|
||||
isRevoked: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if token is valid (not expired, not revoked)
|
||||
*/
|
||||
public isValid(): boolean {
|
||||
if (this.isRevoked) return false;
|
||||
if (this.expiresAt && this.expiresAt < new Date()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record token usage
|
||||
*/
|
||||
public async recordUsage(ip?: string): Promise<void> {
|
||||
this.lastUsedAt = new Date();
|
||||
this.lastUsedIp = ip;
|
||||
this.usageCount += 1;
|
||||
await this.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke token
|
||||
*/
|
||||
public async revoke(reason?: string): Promise<void> {
|
||||
this.isRevoked = true;
|
||||
this.revokedAt = new Date();
|
||||
this.revokedReason = reason;
|
||||
await this.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if token has permission for protocol
|
||||
*/
|
||||
public hasProtocol(protocol: TRegistryProtocol): boolean {
|
||||
return this.protocols.includes(protocol) || this.protocols.includes('*' as TRegistryProtocol);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if token has permission for action on resource
|
||||
*/
|
||||
public hasScope(
|
||||
protocol: TRegistryProtocol,
|
||||
organizationId?: string,
|
||||
repositoryId?: string,
|
||||
action?: string
|
||||
): boolean {
|
||||
for (const scope of this.scopes) {
|
||||
// Check protocol
|
||||
if (scope.protocol !== '*' && scope.protocol !== protocol) continue;
|
||||
|
||||
// Check organization
|
||||
if (scope.organizationId && scope.organizationId !== organizationId) continue;
|
||||
|
||||
// Check repository
|
||||
if (scope.repositoryId && scope.repositoryId !== repositoryId) continue;
|
||||
|
||||
// Check action
|
||||
if (action && !scope.actions.includes('*') && !scope.actions.includes(action as never)) continue;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle hook
|
||||
*/
|
||||
public async beforeSave(): Promise<void> {
|
||||
if (!this.id) {
|
||||
this.id = await ApiToken.getNewId();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user