Skip to content

Commit

Permalink
feat(console): setting houdini session
Browse files Browse the repository at this point in the history
  • Loading branch information
xmlking committed Feb 7, 2024
1 parent b237a1c commit 5305f5b
Show file tree
Hide file tree
Showing 18 changed files with 655 additions and 10 deletions.
9 changes: 5 additions & 4 deletions apps/console/src/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ type AvailableLanguageTag = import('$i18n/runtime').AvailableLanguageTag;

declare global {
namespace App {
// houdini session
interface Session {
token?: string;
roles?: string | string[] | null;
}
namespace Superforms {
type Message = Pick<ToastSettings, 'message' | 'hideDismiss' | 'timeout'> & {
type: 'error' | 'success' | 'warning';
Expand All @@ -21,10 +26,6 @@ declare global {
nhost: NhostClient;
}
interface PageData {
/**
* Client-forwarded locals.lang.
*/
lang: App.Locals['lang'];
// user?: Omit<User, 'userId'>;
/**
* Short-life cookie-persisted flash message.
Expand Down
4 changes: 2 additions & 2 deletions apps/console/src/hooks.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Logger } from '@spectacular/utils';
import { ZodError } from 'zod';
import { GraphQLError } from 'graphql';
import { dev } from '$app/environment';
import { auth, guard, theme } from '$lib/server/middleware';
import { auth, guard, houdini, theme } from '$lib/server/middleware';
import { i18n } from '$lib/i18n';

/**
Expand All @@ -29,7 +29,7 @@ process.on('SIGINT', shutdownGracefully); // Ctrl+C
process.on('SIGTERM', shutdownGracefully); // docker stop

// NOTE: Order is impotent! `auth` middleware sets `nhost` into `local` which is used by `guard` middleware
export const handle: Handle = sequence(i18n.handle(), auth, guard, theme);
export const handle: Handle = sequence(i18n.handle(), auth, guard, houdini, theme);

/**
* handle server-side errors
Expand Down
41 changes: 41 additions & 0 deletions apps/console/src/lib/graphql/MUTATION.CreatePolicy.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
mutation CreatePolicy($data: policies_insert_input!) {
insert_policies_one(object: $data) {
id
weight
active
validFrom
validTo
subjectId
subjectType
subjectDisplayName
subjectSecondaryId
createdBy
createdAt
updatedAt
updatedBy
organization
rule {
id
displayName
description
tags
annotations
shared
source
sourcePort
destination
destinationPort
protocol
direction
action
appId
throttleRate
weight
createdBy
createdAt
updatedAt
updatedBy
organization
}
}
}
15 changes: 15 additions & 0 deletions apps/console/src/lib/graphql/MUTATION.DeletePolicy.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
mutation DeletePolicy($policyId: uuid!, $ruleId: uuid!, $deletedAt: timestamptz!) {
update_policies_by_pk(pk_columns: { id: $policyId }, _set: { deletedAt: $deletedAt }) {
id
}
update_rules(
where: { shared: { _eq: false }, id: { _eq: $ruleId } }
_set: { deletedAt: $deletedAt }
) {
affected_rows
returning {
id
displayName
}
}
}
18 changes: 18 additions & 0 deletions apps/console/src/lib/graphql/MUTATION.UpdatePolicy.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
mutation UpdatePolicy(
$policyId: uuid!
$policyData: policies_set_input!
$ruleId: uuid!
$ruleData: rules_set_input!
$skipRuleUpdate: Boolean = false
) {
update_policies_by_pk(pk_columns: { id: $policyId }, _set: $policyData) {
id
subjectDisplayName
updatedAt
}
update_rules_by_pk(pk_columns: { id: $ruleId }, _set: $ruleData) @skip(if: $skipRuleUpdate) {
id
displayName
updatedAt
}
}
30 changes: 30 additions & 0 deletions apps/console/src/lib/graphql/QUERY.GetPolicy.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
query GetPolicy($id: uuid!) {
policies_by_pk(id: $id) {
weight
active
validFrom
validTo
subjectId
subjectType
subjectDisplayName
subjectSecondaryId
ruleId
rule {
displayName
description
tags
annotations
shared
source
sourcePort
destination
destinationPort
protocol
direction
action
appId
throttleRate
weight
}
}
}
45 changes: 45 additions & 0 deletions apps/console/src/lib/graphql/QUERY.ListPolicies.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# query ListPolicies(
# $limit: Int = 50
# $offset: Int = 0
# $orderBy: [policies_order_by!] = [{ updatedAt: desc_nulls_last }]
# ) {
# policies(order_by: $orderBy, limit: $limit, offset: $offset) {
# ...Policy_list_fields @mask_disable
# }
# }
query ListPolicies(
$where: policies_bool_exp
$limit: Int = 50
$offset: Int = 0
$orderBy: [policies_order_by!] = [{ updatedAt: desc_nulls_last }]
) @cache(policy: NetworkOnly) {
policies(where: $where, order_by: $orderBy, limit: $limit, offset: $offset) {
id
weight
active
validFrom
validTo
subjectId
subjectType
subjectDisplayName
subjectSecondaryId
updatedAt
rule {
id
displayName
description
tags
annotations
shared
source
sourcePort
destination
destinationPort
protocol
direction
action
appId
throttleRate
}
}
}
6 changes: 3 additions & 3 deletions apps/console/src/lib/graphql/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { subscription } from '$houdini/plugins';
import { HoudiniClient } from '$houdini';
import type { ClientPlugin } from '$houdini';

const url = env.PUBLIC_GRAPHQL_ENDPOINT;
const url = env.PUBLIC_GRAPHQL_ENDPOINT!;

const log = new Logger('houdini.client');

Expand Down Expand Up @@ -51,7 +51,7 @@ export default new HoudiniClient({
return {
headers: {
...(token ? { Authorization: `Bearer ${token}` } : {}),
...(useRole ? { 'x-hasura-role': useRole } : { 'x-hasura-role': 'anonymous' }),
...(useRole ? { 'x-hasura-role': useRole } : { 'x-hasura-role': 'public' }),
...(backendToken ? { backendToken } : {})
}
};
Expand All @@ -64,7 +64,7 @@ export default new HoudiniClient({
});

function getHighestRole(roles: string[] | undefined) {
if (!roles) return 'anonymous';
if (!roles) return 'public';
if (roles?.includes('tester')) return 'tester';
if (roles?.includes('manager')) return 'manager';
if (roles?.includes('user')) return 'user';
Expand Down
1 change: 1 addition & 0 deletions apps/console/src/lib/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const menuNavLinks: Record<string, Array<{ title: string; list: List }>>
{ href: '/docs/contributing', label: 'Contributing', keywords: 'branch, pr' },
{ href: '/dashboard/reports', label: 'Reports', keywords: 'reports, graph' },
{ href: '/dashboard/customers', label: 'Customers', keywords: 'customers, users' },
{ href: '/dashboard/policies', label: 'Policies', keywords: 'policies, rules' },
{ href: '/auth/signup', label: 'Signup', keywords: 'signup, users' },
{ href: '/auth/signin', label: 'Signin', keywords: 'signin, login, users' }
]
Expand Down
149 changes: 149 additions & 0 deletions apps/console/src/lib/schema/policy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { z } from 'zod';

/**
* Policy Schema
*/
export const policySchema = z.object({
id: z.string().trim().uuid(),
// validFrom: z.coerce.date(),
// validFrom: z.string().datetime({ offset: true }).nullish().catch(null),
// validTo: z.string().datetime({ offset: true }).nullish().catch(null),
validFrom: z.date().nullish(),
validTo: z.date().nullish(),
weight: z.coerce.number().min(0).max(1000).optional().default(1000),
subjectDisplayName: z.string().trim().min(1),
subjectId: z.string().trim().min(1),
subjectSecondaryId: z.string().trim().min(1),
subjectType: z
.enum(['user', 'group', 'device', 'service_account', 'device_pool'])
.default('user'),
active: z.boolean().optional().default(true),
ruleId: z.string().trim().uuid(),
rule: z.object({
id: z.string().trim().uuid(),
displayName: z.string().trim().min(4).max(256),
description: z.string().trim().max(256).nullish(),
tags: z.string().trim().min(2).array().max(5).nullish(),
// annotations: z.preprocess(stringToJSON, z.record(z.string().trim().min(3), z.string().trim().min(3)).nullish()),
// annotations: z.preprocess(stringToMap, z.map(z.string().trim().min(3), z.string().trim().min(3))).nullish(),
annotations: z.string().trim().nullish(), // TODO: validate map string
source: z.string().ip().nullish(),
sourcePort: z.string().trim().nullish(),
destination: z.string().ip().nullish(),
destinationPort: z.string().trim().nullish(),
protocol: z
.enum(['Any', 'IP', 'ICMP', 'IGMP', 'TCP', 'UDP', 'IPV6', 'ICMPV6', 'RM'])
.default('Any'),
action: z
.enum(['permit', 'block', 'callout_inspection', 'callout_terminating', 'callout_unknown'])
.default('block'),
direction: z.enum(['egress', 'ingress']).default('egress'),
appId: z.string().trim().nullish(),
throttleRate: z.coerce.number().min(0).max(100).optional().default(80),
weight: z.coerce.number().min(0).max(1000).optional().default(1000),
shared: z.boolean().optional().default(false)
})
});

export type PolicySchema = typeof policySchema;
export type Policy = z.infer<typeof policySchema>;

/**
* Search Policy Schema
*/
export const policySearchSchema = z.object({
limit: z.number().int().min(5).max(100).default(10),
offset: z.number().int().min(0).default(0),
// TODO use enum
subjectType: z.enum(['user', 'group', 'device', 'service_account', 'device_pool']).optional(),
subjectId: z.string().trim().uuid().optional(),
subjectDisplayName: z.string().trim().optional()
});

/**
* Create Policy Schema
*/
export const createPolicySchema = policySchema
.omit({
id: true
// rule: {
// id: true
// }
})
.extend({
ruleId: policySchema.shape.ruleId.nullish(),
// FIXME: omit for role.id=true not working
rule: policySchema.shape.rule.extend({
id: policySchema.shape.rule.shape.id.optional()
})
})
.superRefine((data, ctx) => checkValidDates(ctx, data.validFrom, data.validTo))
.superRefine((data, ctx) => checkForMissingRule(ctx, data.ruleId, data.rule));

export type CreatePolicySchema = typeof createPolicySchema;
export type CreatePolicy = z.infer<typeof createPolicySchema>;
export const createPolicyKeys = createPolicySchema.innerType().innerType().keyof().Enum;

/**
* Update Policy Schema
*/
export const updatePolicySchema = policySchema
.omit({
id: true
// rule: {
// id: true
// }
})
.extend({
// FIXME: omit for role.id=true not working
rule: policySchema.shape.rule.extend({
id: policySchema.shape.rule.shape.id.optional()
}),
originalShared: policySchema.shape.rule.shape.shared
})
.superRefine((data, ctx) => checkValidDates(ctx, data.validFrom, data.validTo));

export type UpdatePolicySchema = typeof updatePolicySchema;
export type UpdatePolicy = z.infer<typeof updatePolicySchema>;
export const updatePolicyKeys = updatePolicySchema.innerType().keyof().Enum;

/**
* Refine functions
*/

function checkValidStringDates(
ctx: z.RefinementCtx,
validFrom: string | undefined | null,
validTo: string | undefined | null
) {
if (validFrom && validTo && new Date(validTo) < new Date(validFrom)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ['validTo'],
message: 'validTo should be after validFrom'
});
}
}

function checkValidDates(
ctx: z.RefinementCtx,
validFrom: Date | undefined | null,
validTo: Date | undefined | null
) {
if (validFrom && validTo && validTo < validFrom) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ['validTo'],
message: 'validTo should be after validFrom'
});
}
}
function checkForMissingRule(ctx: z.RefinementCtx, ruleId: string | undefined | null, rule: any) {
if (ruleId == null && rule == null) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ['ruleId'],
message: 'Rule is required'
});
}
}
2 changes: 1 addition & 1 deletion apps/console/src/lib/server/middleware/guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const guard = (async ({ event, resolve }) => {

const {
url: { pathname },
locals: { lang, nhost }
locals: { paraglide: { lang }, nhost }
} = event;
const canonicalPath = i18n.route(pathname);
// bypass guard for all unprotected routes.
Expand Down
1 change: 1 addition & 0 deletions apps/console/src/lib/server/middleware/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { theme } from './theme';
export { logger } from './logger';
export { auth } from './auth';
export { guard } from './guard';
export { houdini } from './houdini';
Loading

0 comments on commit 5305f5b

Please sign in to comment.