Skip to content

Commit

Permalink
fix login & permission editor of users
Browse files Browse the repository at this point in the history
  • Loading branch information
Seania committed Jan 13, 2023
1 parent 2ebeb98 commit 853571d
Show file tree
Hide file tree
Showing 27 changed files with 504 additions and 118 deletions.
39 changes: 30 additions & 9 deletions src/hooks.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ import {
version as versionUuid,
} from '$lib/uuid/esm-node';
import {env} from 'node:process';
import {isEmpty} from 'lodash-es';
import {Permissions} from '$lib/community/permission';

global.atob = atob;
global.btoa = btoa;


// noinspection JSUnusedGlobalSymbols
export const handle = sequence(init, ui, images, auth, setSessionId);
export const handle = sequence(init, ui, images, auth, setSessionId, setPermissions);

function init({event, resolve}: HandleParameters): MaybePromise<Response> {
if (!event.locals) {
Expand Down Expand Up @@ -157,14 +158,14 @@ function images({event, resolve}: HandleParameters): MaybePromise<Response> {
return resolve(event);
}

function resolveJwt(jwt?: Jwt): IUserSession | undefined {
function resolveJwt(jwt?: Jwt, scope: 'user' | 'refresh' = 'user'): IUserSession | undefined {
if (!jwt) {
return undefined;
}

const body = jwt.body.toJSON();

if (body.iss !== 'https://ru.hn/' || body.scope !== 'user') {
if (body.iss !== 'https://ru.hn/' || body.scope !== scope) {
return undefined;
}

Expand All @@ -177,7 +178,7 @@ async function auth({event, resolve}: HandleParameters): Promise<Response> {

if (token) {
const jwt = njwt.verify(token, key);
const body = resolveJwt(jwt);
const body = resolveJwt(jwt, 'user');

if (!body) {
event.locals.user = body;
Expand All @@ -190,16 +191,19 @@ async function auth({event, resolve}: HandleParameters): Promise<Response> {

if (refresh) {
const jwt = njwt.verify(refresh, key);
const body = resolveJwt(jwt);
const body = resolveJwt(jwt, 'refresh');

if (!body) {
event.locals.user = body;
return resolve(event);
}

const {uid} = body;
const user = await User.findByUniqueId(uid);
const newToken = await user?.token('user');
const {sub} = body;
const user = User.fromSub(sub);
const newToken = await user?.token('user', {
rank: await user.rank,
adult: await user.isAdult(),
});
if (newToken) {
event.cookies.set('token', newToken.compact(), {
path: '/',
Expand All @@ -210,6 +214,8 @@ async function auth({event, resolve}: HandleParameters): Promise<Response> {
});

event.locals.user = newToken.body.toJSON() as any;
} else {
console.error('no token?');
}
}
}
Expand Down Expand Up @@ -238,6 +244,21 @@ async function setSessionId({event, resolve}: HandleParameters): Promise<Respons
return response;
}

/** @type {import('@sveltejs/kit').Handle}
* 사용자에게
*/
async function setPermissions({event, resolve}: HandleParameters): Promise<Response> {
if (event.locals?.user) {
const user = User.fromSub(event.locals.user.sub)!;
const {permissions} = user;
const all = await permissions.getAll();
if (isEmpty(all)) {
permissions.set(Permissions.DefaultPermissions).then();
}
}
return resolve(event);
}

interface HandleParameters {
event: RequestEvent,
resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>
Expand Down
16 changes: 16 additions & 0 deletions src/lib/auth/user/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@ import type {IUser} from '$lib/types/user';
import {isStringInteger} from '$lib/util';
import type {IArangoDocumentIdentifier} from '$lib/database';
import {Notifications} from '$lib/notifications/server';
import {Permissions} from '$lib/community/permission';
import {last} from 'lodash-es';
import {error} from '@sveltejs/kit';
import HttpStatus from 'http-status-codes';

type UnsafeUser = IUser & { password: string };

export class User {
private readonly notifications: Notifications;
readonly permissions: Permissions;
constructor(readonly id: string) {
this.notifications = new Notifications(this);
this.permissions = new Permissions(null, this);
}

private stored: UnsafeUser | undefined;
Expand Down Expand Up @@ -90,6 +96,11 @@ export class User {
});
}

static fromSub(sub: string): User | null {
const id = last(sub.split('/'));
return id ? new User(id) : null;
}

static async findByUniqueId(uid?: string): Promise<User | null> {
// console.trace('3');
if (!uid) {
Expand Down Expand Up @@ -137,6 +148,11 @@ export class User {
throw Error('password invalid (is that shorten than 6)');
}

const regex = /^[a-zA-Z0-9--_]+$/;
if (!regex.test(this.id)) {
throw error(HttpStatus.BAD_REQUEST, 'Not allowed character found');
}

const hashed = await argon2.hash(password);

await db.query(aql`
Expand Down
2 changes: 1 addition & 1 deletion src/lib/bitfield.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,6 @@ export class BitField {
}
}

export function BitFlag(x: bigint | number) {
export function BitFlag(x: bigint | number): bigint {
return BigInt(1) << BigInt(x);
}
2 changes: 1 addition & 1 deletion src/lib/community/article/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {uploadAllowedExtensions} from '$lib/file/image/shared';
import {env} from 'node:process';

export class Article {

readonly type = 'article';
title: string | undefined;
content: string | undefined;

Expand Down
4 changes: 3 additions & 1 deletion src/lib/community/board/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ import {inRange} from 'lodash-es';
type BoardType = 'default' | 'best';

export class Board {
constructor(private readonly id: string) {
readonly type = 'board';

constructor(readonly id: string) {
}

static async listAll() {
Expand Down
3 changes: 2 additions & 1 deletion src/lib/community/comment/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import db from '$lib/database/instance';
import {aql} from 'arangojs';

export class Comment {
constructor(private readonly id: string) {
readonly type = 'comment';
constructor(readonly id: string) {
}

static async new(relative: Article, author: User, content: string): Promise<Comment> {
Expand Down
153 changes: 103 additions & 50 deletions src/lib/community/permission/index.ts
Original file line number Diff line number Diff line change
@@ -1,77 +1,130 @@
import {Board} from '$lib/community/board/server';
import {Article} from '$lib/community/article/server';
import {Comment} from '$lib/community/comment/server';
import type {Board} from '$lib/community/board/server';
import type {Article} from '$lib/community/article/server';
import type {Comment} from '$lib/community/comment/server';
import {User} from '$lib/auth/user/server';
import {EUserRanks} from '$lib/types/user-ranks';
import {BitField, type BitFieldResolvable, BitFlag} from '$lib/bitfield';
import db from '$lib/database/instance';
import {aql} from 'arangojs';
import {flags} from '$lib/community/permission/shared/flags';

export class Permission extends BitField {
export class Permissions {
private user: User;
constructor(bits: BitFieldResolvable = 0, private context: Board | Article | Comment, user: string | User) {
super(bits);
constructor(private context: Board | Article | Comment | null, user: string | User) {
if (user instanceof User) {
this.user = user;
} else {
this.user = new User(user);
}
}

static FLAGS = {
READ_POST: BitFlag(0),
WRITE_POST: BitFlag(1),
EDIT_POST: BitFlag(2),
DELETE_POST: BitFlag(3),
MANAGE_POST: BitFlag(4),
VIEW_LIST: BitFlag(5),
READ_COMMENT: BitFlag(6),
VIEW_BOARDLIST: BitFlag(7),
WRITE_COMMENT: BitFlag(8),
EDIT_COMMENT: BitFlag(9),
DELETE_COMMENT: BitFlag(10),
MANAGE_COMMENT: BitFlag(11),
NEW_BOARD: BitFlag(12),
DELETE_BOARD: BitFlag(13),
RENAME_BOARD: BitFlag(14),
LOCK_BOARD: BitFlag(15),
BAN_USER_PERMANENTLY: BitFlag(16),
BAN_USER_TEMPORARY: BitFlag(17),
REORDER_BOARD: BitFlag(18),
ADULT: BitFlag(19),
CHANGE_PUB_BOARD: BitFlag(20),
CHANGE_USER_GRANT: BitFlag(21),
CHANGE_USER_PERMISSION: BitFlag(22),
CHANGE_USERNAME: BitFlag(23),
ADD_TAG: BitFlag(24),
REMOVE_TAG: BitFlag(25),
REMOVE_OWN_TAG: BitFlag(26),
VIEW_TAG_OWNER: BitFlag(27),
};

async own(permission: Permissions): Promise<boolean> {
return new Promise(async (resolve, reject) => {
static readonly FLAGS = flags;

static readonly DefaultPermissions =
Permissions.FLAGS.WRITE_ARTICLE |
Permissions.FLAGS.READ_ARTICLE |
Permissions.FLAGS.EDIT_ARTICLE |
Permissions.FLAGS.DELETE_ARTICLE |
Permissions.FLAGS.READ_COMMENT |
Permissions.FLAGS.EDIT_COMMENT |
Permissions.FLAGS.WRITE_COMMENT |
Permissions.FLAGS.DELETE_COMMENT |
Permissions.FLAGS.VIEW_BOARDLIST |
Permissions.FLAGS.VIEW_ARTICLELIST
;

private check(source: bigint, compare: bigint): boolean {
// eslint-disable-next-line no-extra-boolean-cast
return !!(source & Permissions.FLAGS.ALL) ? true : !!(source & compare);
}

hasOwn(permission: bigint): Promise<boolean> {
return new Promise(async (resolve) => {
if (await this.user.rank <= EUserRanks.Banned) {
return false;
return resolve(false);
}

if (this.context instanceof Board) {

} else if (this.context instanceof Article) {
if (await this.user.rank >= EUserRanks.Admin) {
return resolve(true);
}

} else if (this.context instanceof Comment) {
const result = await this.get();

if (!result) {
return resolve(false);
}
return false;

return resolve(this.check(result, permission));
});
}

get isReadable(): Promise<boolean> {
return new Promise(() => {
async set(permission: bigint) {
const search = {
user: await this.user.uid,
context: this.context?.type ?? null,
target: this.context?.id ?? null,
};
const newPermission = {
...search,
permissions: permission.toString(),
};
return await db.query(aql`
upsert ${search} insert ${newPermission} update ${{permissions: permission.toString()}} in permissions`);
}

});
async enable(permission: bigint) {
const current = await this.get() ?? Permissions.DefaultPermissions;
if (!this.check(current, permission)) {
await this.set(current ^ permission);
}
}

async disable(permission: bigint) {
const current = await this.get() ?? Permissions.DefaultPermissions;
if (this.check(current, permission)) {
await this.set(current ^ permission);
}
}

async get(): Promise<bigint | null> {
const search = {
user: await this.user.uid,
context: this.context?.type ?? null,
target: this.context?.id ?? null,
};

const cursor = await db.query(aql`
for p in permissions
filter p.user == ${search.user} && p.context == ${search.context} && p.target == ${search.target}
return p`);

const data = await cursor.next();

console.log(data);

if (!data) {
return null;
}

return BigInt(data.permissions);
}

async getAll() {
const search = {
user: await this.user.uid,
context: this.context?.type ?? null,
target: this.context?.id ?? null,
};

const cursor = await db.query(aql`
for p in permissions
filter p.user == ${search.user}
return p`);

return await cursor.all();
}

}

export type Permissions = 'read' | 'write' | 'delete' | 'edit';
export type UserPermissions = boolean | AdultCertification | [boolean, AdultCertification];
enum AdultCertification {
None,
Expand Down
Loading

0 comments on commit 853571d

Please sign in to comment.