| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- import { noop, pull } from 'lodash'
- import { ChevronLeft } from 'lucide-react'
- import { useEffect, useState } from 'react'
- import { toast } from 'sonner'
- import { Modal } from 'ui'
- import {
- applyBucketIdToTemplateDefinition,
- createPayloadsForAddPolicy,
- createSQLPolicies,
- } from '../Storage.utils'
- import { STORAGE_POLICY_TEMPLATES } from './StoragePolicies.constants'
- import StoragePoliciesEditor from './StoragePoliciesEditor'
- import StoragePoliciesReview from './StoragePoliciesReview'
- import { POLICY_MODAL_VIEWS } from '@/components/interfaces/Auth/Policies/Policies.constants'
- import PolicySelection from '@/components/interfaces/Auth/Policies/PolicySelection'
- import PolicyTemplates from '@/components/interfaces/Auth/Policies/PolicyTemplates'
- import { DocsButton } from '@/components/ui/DocsButton'
- import { DOCS_URL } from '@/lib/constants'
- const newPolicyTemplate: any = {
- name: '',
- roles: [],
- policyIds: [],
- definition: '',
- allowedOperations: [],
- }
- export const StoragePoliciesEditPolicyModal = ({
- visible = false,
- bucketName = '',
- onSelectCancel = () => {},
- onCreatePolicies = () => {},
- onSaveSuccess = () => {},
- }: any) => {
- const [previousView, setPreviousView] = useState('') // Mainly to decide which view to show when back from templates
- const [view, setView] = useState('')
- const [policyFormFields, setPolicyFormFields] = useState(newPolicyTemplate)
- const [policyStatementsForReview, setPolicyStatementsForReview] = useState<any[]>([])
- useEffect(() => {
- if (visible) {
- onViewIntro()
- setPolicyFormFields(newPolicyTemplate)
- }
- }, [visible])
- /* Methods to determine which step to show */
- const onViewIntro = () => setView(POLICY_MODAL_VIEWS.SELECTION)
- const onViewEditor = (state?: any) => {
- if (state === 'new') {
- setPolicyFormFields({
- ...policyFormFields,
- definition: `bucket_id = '${bucketName}'`,
- })
- }
- setView(POLICY_MODAL_VIEWS.EDITOR)
- }
- const onViewTemplates = () => {
- setPreviousView(view)
- setView(POLICY_MODAL_VIEWS.TEMPLATES)
- }
- const onReviewPolicy = () => setView(POLICY_MODAL_VIEWS.REVIEW)
- /* Methods for policy templates */
- const onSelectBackFromTemplates = () => setView(previousView)
- const onUseTemplate = (template: any) => {
- // Each template has an id as a unique identifier to refresh the SQL editor
- // but we don't need this property to be in the policyFormField
- const { id, ...templateFields } = template
- const definition = applyBucketIdToTemplateDefinition(templateFields.definition, bucketName)
- setPolicyFormFields({
- ...policyFormFields,
- ...templateFields,
- definition: definition,
- })
- onViewEditor()
- }
- /* Methods for policy editor form fields */
- const onUpdatePolicyName = (name: string) => {
- if (name.length <= 50) {
- setPolicyFormFields({
- ...policyFormFields,
- name,
- })
- }
- }
- const onUpdatePolicyDefinition = (definition: any) => {
- setPolicyFormFields({
- ...policyFormFields,
- definition,
- })
- }
- const onToggleOperation = (operation: any, isSingleOperation = false) => {
- if (isSingleOperation) {
- return setPolicyFormFields({
- ...policyFormFields,
- allowedOperations: [operation],
- })
- }
- const currentOps = policyFormFields.allowedOperations
- const isRemoving = currentOps.includes(operation)
- let updatedAllowedOperations = isRemoving
- ? pull(currentOps.slice(), operation)
- : currentOps.concat([operation])
- if (!isRemoving && (operation === 'UPDATE' || operation === 'DELETE')) {
- if (!updatedAllowedOperations.includes('SELECT')) {
- updatedAllowedOperations = updatedAllowedOperations.concat(['SELECT'])
- }
- }
- if (isRemoving && operation === 'SELECT') {
- updatedAllowedOperations = updatedAllowedOperations.filter(
- (op: string) => op !== 'UPDATE' && op !== 'DELETE'
- )
- }
- return setPolicyFormFields({
- ...policyFormFields,
- allowedOperations: updatedAllowedOperations,
- })
- }
- const onUpdatePolicyRoles = (roles: any) => {
- setPolicyFormFields({
- ...policyFormFields,
- roles,
- })
- }
- const validatePolicyEditorFormFields = () => {
- const { name, definition, allowedOperations } = policyFormFields
- if (name.length === 0) {
- return toast.error('Please provide a name for your policy')
- }
- if (definition.length === 0) {
- // Will need to figure out how to strip away comments or something
- return toast.error('Please provide a definition for your policy')
- }
- if (allowedOperations.length === 0) {
- return toast.error('Please allow at least one operation in your policy')
- }
- const policySQLStatements = createSQLPolicies(bucketName, policyFormFields)
- setPolicyStatementsForReview(policySQLStatements)
- onReviewPolicy()
- }
- /* Create policy payloads to be sent upstream to API endpoint */
- const onReviewSave = () => {
- const payloads = createPayloadsForAddPolicy(bucketName, policyFormFields)
- onSavePolicy(payloads)
- }
- const onSavePolicy = async (payloads: any) => {
- const errors = await onCreatePolicies(payloads)
- const hasErrors = errors.indexOf(true) !== -1
- if (hasErrors) {
- onViewEditor()
- } else {
- onSaveSuccess()
- }
- }
- /* Misc components */
- const StoragePolicyEditorModalTitle = ({
- view,
- bucketName,
- onSelectBackFromTemplates = noop,
- }: any) => {
- const getTitle = () => {
- if (view === POLICY_MODAL_VIEWS.EDITOR || view === POLICY_MODAL_VIEWS.SELECTION) {
- return `Adding new policy to ${bucketName}`
- }
- if (view === POLICY_MODAL_VIEWS.REVIEW) {
- return `Reviewing policies to be created for ${bucketName}`
- }
- }
- if (view === POLICY_MODAL_VIEWS.TEMPLATES) {
- return (
- <div>
- <div className="flex items-center space-x-3">
- <span
- onClick={onSelectBackFromTemplates}
- className="cursor-pointer text-foreground-lighter transition-colors hover:text-foreground"
- >
- <ChevronLeft strokeWidth={2} size={14} />
- </span>
- <h4 className="textlg m-0">Select a template to use for your new policy</h4>
- </div>
- </div>
- )
- }
- return (
- <div className="w-full flex items-center justify-between gap-x-2 pr-6">
- <h4 className="m-0 truncate">{getTitle()}</h4>
- <DocsButton href={`${DOCS_URL}/learn/auth-deep-dive/auth-policies`} />
- </div>
- )
- }
- return (
- <Modal
- hideFooter
- className="[&>div:first-child]:py-3"
- size={view === POLICY_MODAL_VIEWS.SELECTION ? 'medium' : 'xxlarge'}
- visible={visible}
- contentStyle={{ padding: 0 }}
- header={[
- <StoragePolicyEditorModalTitle
- key="0"
- view={view}
- bucketName={bucketName}
- onSelectBackFromTemplates={onSelectBackFromTemplates}
- />,
- ]}
- onCancel={onSelectCancel}
- >
- <div className="w-full">
- {view === POLICY_MODAL_VIEWS.SELECTION ? (
- <PolicySelection
- description="PostgreSQL policies control access to your files and folders"
- onViewTemplates={onViewTemplates}
- onViewEditor={() => onViewEditor('new')}
- showAssistantPreview={false}
- />
- ) : view === POLICY_MODAL_VIEWS.EDITOR ? (
- <StoragePoliciesEditor
- policyFormFields={policyFormFields}
- onViewTemplates={onViewTemplates}
- onUpdatePolicyName={onUpdatePolicyName}
- onUpdatePolicyDefinition={onUpdatePolicyDefinition}
- onToggleOperation={onToggleOperation}
- onUpdatePolicyRoles={onUpdatePolicyRoles}
- onReviewPolicy={validatePolicyEditorFormFields}
- />
- ) : view === POLICY_MODAL_VIEWS.TEMPLATES ? (
- <PolicyTemplates
- templates={STORAGE_POLICY_TEMPLATES as any[]}
- onUseTemplate={onUseTemplate}
- templatesNote={''}
- />
- ) : view === POLICY_MODAL_VIEWS.REVIEW ? (
- <StoragePoliciesReview
- policyStatements={policyStatementsForReview}
- onSelectBack={onViewEditor}
- onSelectSave={onReviewSave}
- />
- ) : (
- <div />
- )}
- </div>
- </Modal>
- )
- }
|