WIP: delegation.

This commit is contained in:
Philippe Pittoli 2025-07-07 22:53:37 +02:00
parent 37ebd5566a
commit 32aba841f4
6 changed files with 106 additions and 0 deletions

View file

@ -970,6 +970,12 @@ decode_message_from_dnsmanagerd arraybuffer = do
DNSManager.MkFoundDomains response -> do DNSManager.MkFoundDomains response -> do
handleAction $ Log $ SuccessLog "Received found domain list." handleAction $ Log $ SuccessLog "Received found domain list."
H.tell _admini unit (PageAdministration.GotFoundDomains response.domains) H.tell _admini unit (PageAdministration.GotFoundDomains response.domains)
DNSManager.MkDomainDelegated response -> do
handleAction $ Log $ SuccessLog $
"Domain " <> response.domain
<> " is now delegated (to " <> response.nameserver1 <> " and " <> response.nameserver2 <> ")."
handleAction $ Log $ SuccessLog $
"FIXME: remove all RRs from the domain, display recorded nameservers and a revert button."
(DNSManager.GotKeepAlive _) -> do (DNSManager.GotKeepAlive _) -> do
-- handleAction $ Log $ SystemLog $ "KeepAlive." -- handleAction $ Log $ SystemLog $ "KeepAlive."
pure unit pure unit

View file

@ -133,6 +133,13 @@ type SearchDomain = { domain :: String, offset :: Maybe Int }
codecSearchDomain ∷ CA.JsonCodec SearchDomain codecSearchDomain ∷ CA.JsonCodec SearchDomain
codecSearchDomain = CA.object "SearchDomain" (CAR.record { domain: CA.string, offset: CAR.optional CA.int }) codecSearchDomain = CA.object "SearchDomain" (CAR.record { domain: CA.string, offset: CAR.optional CA.int })
{- 25 -}
type DelegateDomain = { domain :: String, nameserver1 :: String, nameserver2 :: String }
codecDelegateDomain ∷ CA.JsonCodec DelegateDomain
codecDelegateDomain = CA.object "DelegateDomain" (CAR.record { domain: CA.string
, nameserver1: CA.string
, nameserver2: CA.string })
{- 100 -} {- 100 -}
type GenerateAllZoneFiles = {} type GenerateAllZoneFiles = {}
codecGenerateAllZoneFiles ∷ CA.JsonCodec GenerateAllZoneFiles codecGenerateAllZoneFiles ∷ CA.JsonCodec GenerateAllZoneFiles
@ -285,6 +292,15 @@ type OrphanDomainList = { domains :: Array String }
codecOrphanDomainList ∷ CA.JsonCodec OrphanDomainList codecOrphanDomainList ∷ CA.JsonCodec OrphanDomainList
codecOrphanDomainList = CA.object "OrphanDomainList" (CAR.record { domains: CA.array CA.string }) codecOrphanDomainList = CA.object "OrphanDomainList" (CAR.record { domains: CA.array CA.string })
{- 25 same as 14 -}
{- 26 -}
type DomainDelegated = { domain :: String, nameserver1 :: String, nameserver2 :: String }
codecDomainDelegated ∷ CA.JsonCodec DomainDelegated
codecDomainDelegated = CA.object "DomainDelegated" (CAR.record { domain: CA.string
, nameserver1: CA.string
, nameserver2: CA.string })
{- 50 -} {- 50 -}
type UnknownUser = { } type UnknownUser = { }
codecUnknownUser ∷ CA.JsonCodec UnknownUser codecUnknownUser ∷ CA.JsonCodec UnknownUser
@ -328,6 +344,7 @@ data RequestMessage
| MkAskUnShareDomain AskUnShareDomain -- 22 | MkAskUnShareDomain AskUnShareDomain -- 22
| MkGainOwnership GainOwnership -- 23 | MkGainOwnership GainOwnership -- 23
| MkSearchDomain SearchDomain -- 24 | MkSearchDomain SearchDomain -- 24
| MkDelegateDomain DelegateDomain -- 25
| MkGenerateAllZoneFiles GenerateAllZoneFiles -- 100 | MkGenerateAllZoneFiles GenerateAllZoneFiles -- 100
| MkGenerateZoneFile GenerateZoneFile -- 101 | MkGenerateZoneFile GenerateZoneFile -- 101
| MkKeepAlive KeepAlive -- 250 | MkKeepAlive KeepAlive -- 250
@ -360,6 +377,7 @@ data AnswerMessage
| MkGeneratedZoneFile GeneratedZoneFile -- 23 | MkGeneratedZoneFile GeneratedZoneFile -- 23
| MkOrphanDomainList OrphanDomainList -- 24 | MkOrphanDomainList OrphanDomainList -- 24
| MkFoundDomains DomainList -- 25 | MkFoundDomains DomainList -- 25
| MkDomainDelegated DomainDelegated -- 26
| MkUnknownUser UnknownUser -- 50 | MkUnknownUser UnknownUser -- 50
| MkNoOwnership NoOwnership -- 51 | MkNoOwnership NoOwnership -- 51
| MkInsufficientRights InsufficientRights -- 52 | MkInsufficientRights InsufficientRights -- 52
@ -387,6 +405,7 @@ encode m = case m of
(MkAskUnShareDomain request) -> get_tuple 22 codecAskUnShareDomain request (MkAskUnShareDomain request) -> get_tuple 22 codecAskUnShareDomain request
(MkGainOwnership request) -> get_tuple 23 codecGainOwnership request (MkGainOwnership request) -> get_tuple 23 codecGainOwnership request
(MkSearchDomain request) -> get_tuple 24 codecSearchDomain request (MkSearchDomain request) -> get_tuple 24 codecSearchDomain request
(MkDelegateDomain request) -> get_tuple 25 codecDelegateDomain request
(MkGenerateAllZoneFiles request) -> get_tuple 100 codecGenerateAllZoneFiles request (MkGenerateAllZoneFiles request) -> get_tuple 100 codecGenerateAllZoneFiles request
(MkGenerateZoneFile request) -> get_tuple 101 codecGenerateZoneFile request (MkGenerateZoneFile request) -> get_tuple 101 codecGenerateZoneFile request
(MkKeepAlive request) -> get_tuple 250 codecKeepAlive request (MkKeepAlive request) -> get_tuple 250 codecKeepAlive request
@ -428,6 +447,7 @@ decode number string
23 -> error_management codecGeneratedZoneFile MkGeneratedZoneFile 23 -> error_management codecGeneratedZoneFile MkGeneratedZoneFile
24 -> error_management codecOrphanDomainList MkOrphanDomainList 24 -> error_management codecOrphanDomainList MkOrphanDomainList
25 -> error_management codecDomainList MkFoundDomains 25 -> error_management codecDomainList MkFoundDomains
26 -> error_management codecDomainDelegated MkDomainDelegated
50 -> error_management codecUnknownUser MkUnknownUser 50 -> error_management codecUnknownUser MkUnknownUser
51 -> error_management codecNoOwnership MkNoOwnership 51 -> error_management codecNoOwnership MkNoOwnership
52 -> error_management codecInsufficientRights MkInsufficientRights 52 -> error_management codecInsufficientRights MkInsufficientRights

View file

@ -42,6 +42,7 @@ import App.Text.Explanations as Explanations
import App.Type.RRId (RRId) import App.Type.RRId (RRId)
import App.Type.Field as Field import App.Type.Field as Field
import App.Type.Delegation (mkEmptyDelegationForm, update_delegation_field, Form, Field) as Delegation
import App.Type.RRModal (RRModal(..)) import App.Type.RRModal (RRModal(..))
import App.Type.AcceptedRRTypes (AcceptedRRTypes(..)) import App.Type.AcceptedRRTypes (AcceptedRRTypes(..))
import App.Type.ResourceRecord (ResourceRecord) import App.Type.ResourceRecord (ResourceRecord)
@ -105,6 +106,9 @@ data Action
-- | Create a new resource record modal (a form) for a certain type of component. -- | Create a new resource record modal (a form) for a certain type of component.
| CreateNewRRModal AcceptedRRTypes | CreateNewRRModal AcceptedRRTypes
-- | Delegation modal.
| CreateDelegationModal
-- | Create modal (a form) for a resource record to update. -- | Create modal (a form) for a resource record to update.
| CreateUpdateRRModal RRId | CreateUpdateRRModal RRId
@ -120,6 +124,9 @@ data Action
-- | Update new entry form (in the `rr_modal` modal). -- | Update new entry form (in the `rr_modal` modal).
| UpdateCurrentRR Field.Field | UpdateCurrentRR Field.Field
-- | Update a delegation form field (new nameservers for the domain).
| UpdateDelegationForm Delegation.Field
-- | Validate a new resource record before adding it. -- | Validate a new resource record before adding it.
| ValidateRR AcceptedRRTypes | ValidateRR AcceptedRRTypes
@ -169,6 +176,9 @@ type State =
-- Unique RR form. -- Unique RR form.
, _rr_form :: RRForm , _rr_form :: RRForm
-- DelegationForm
, _delegation_form :: Delegation.Form
, current_tab :: Tab , current_tab :: Tab
} }
@ -200,6 +210,8 @@ initialState domain =
, _rr_form: mkEmptyRRForm , _rr_form: mkEmptyRRForm
, _delegation_form: Delegation.mkEmptyDelegationForm
, current_tab: Zone , current_tab: Zone
} }
@ -227,11 +239,16 @@ render state
= Modal.current_rr_modal state._domain state._rr_form state.rr_modal = Modal.current_rr_modal state._domain state._rr_form state.rr_modal
UpdateCurrentRR NewToken RRUpdate ValidateRR ValidateLocal CancelModal UpdateCurrentRR NewToken RRUpdate ValidateRR ValidateLocal CancelModal
delegation_modal
= Modal.delegation_modal state._domain state._delegation_form
UpdateDelegationForm CancelModal
render_zone = render_zone =
case state.rr_modal of case state.rr_modal of
RemoveRRModal rr_id -> Modal.modal_rr_delete rr_id RemoveRR CancelModal RemoveRRModal rr_id -> Modal.modal_rr_delete rr_id RemoveRR CancelModal
NewRRModal _ -> call_to_current_rr_modal NewRRModal _ -> call_to_current_rr_modal
UpdateRRModal -> call_to_current_rr_modal UpdateRRModal -> call_to_current_rr_modal
DelegationModal -> delegation_modal
NoModal -> HH.div_ NoModal -> HH.div_
[ Web.level [ Web.btn_ [C.is_large, C.is_info] "Back to the domain list" ReturnToDomainList [ Web.level [ Web.btn_ [C.is_large, C.is_info] "Back to the domain list" ReturnToDomainList
, Web.h1 state._domain , Web.h1 state._domain
@ -296,6 +313,10 @@ handleAction = case _ of
state <- H.get state <- H.get
H.modify_ _ { rr_modal = NewRRModal t, _rr_form { _rr = default_rr t state._domain } } H.modify_ _ { rr_modal = NewRRModal t, _rr_form { _rr = default_rr t state._domain } }
-- | Delegation modal presents a simple form with two entries (chosen nameservers).
CreateDelegationModal -> do
H.modify_ _ { rr_modal = DelegationModal, _delegation_form = Delegation.mkEmptyDelegationForm }
-- | Initialize the Zone component: ask for the domain zone to `dnsmanagerd`. -- | Initialize the Zone component: ask for the domain zone to `dnsmanagerd`.
Initialize -> do Initialize -> do
{ _domain } <- H.get { _domain } <- H.get
@ -356,6 +377,12 @@ handleAction = case _ of
let newRR = update_field state._rr_form._rr field let newRR = update_field state._rr_form._rr field
H.modify_ _ { _rr_form { _rr = newRR } } H.modify_ _ { _rr_form { _rr = newRR } }
-- | Update the delegation form.
UpdateDelegationForm field -> do
state <- H.get
let newDelegationForm = Delegation.update_delegation_field state._delegation_form field
H.modify_ _ { _delegation_form = newDelegationForm }
-- | Validate any local RR with the new `_resources` and `_local_errors`. -- | Validate any local RR with the new `_resources` and `_local_errors`.
ValidateLocal -> do ValidateLocal -> do
-- In case the `name` part of the resource record is empty, consider the name to be the domain itself. -- In case the `name` part of the resource record is empty, consider the name to be the domain itself.
@ -501,6 +528,12 @@ render_new_records _
, Web.btn "DMARC" (CreateNewRRModal DMARC) , Web.btn "DMARC" (CreateNewRRModal DMARC)
] [] ] []
, Web.hr , Web.hr
, Web.h1 "Delegation"
-- use "level" to get horizontal buttons next to each other (probably vertical on mobile)
, Web.level [
Web.btn "Delegate your domain to different name servers" CreateDelegationModal
] []
, Web.hr
, Web.level [ , Web.level [
Web.btn "Get the final zone file" AskGeneratedZoneFile Web.btn "Get the final zone file" AskGeneratedZoneFile
] [HH.text "For debug purposes. ⚠"] ] [HH.text "For debug purposes. ⚠"]

View file

@ -21,6 +21,7 @@ import App.Type.RRId (RRId)
import App.Type.DMARC as DMARC import App.Type.DMARC as DMARC
import App.Type.DKIM as DKIM import App.Type.DKIM as DKIM
import App.Type.Field as Field import App.Type.Field as Field
import App.Type.Delegation as Delegation
import App.Templates.Table as Table import App.Templates.Table as Table
import Data.String (toLower) import Data.String (toLower)
@ -51,6 +52,35 @@ modal_rr_delete rr_id action_remove_rr action_cancel_modal = Web.modal "Deleting
zip_nullable :: forall a. Array a -> Array String -> Array (Tuple a String) zip_nullable :: forall a. Array a -> Array String -> Array (Tuple a String)
zip_nullable txt raw = A.zip txt ([""] <> raw) zip_nullable txt raw = A.zip txt ([""] <> raw)
type ActionUpdateDelegationForm i = (Delegation.Field -> i)
delegation_modal :: forall w i.
Domain -> Delegation.Form -> ActionUpdateDelegationForm i -> ActionCancelModal i -> HH.HTML w i
delegation_modal selected_domain form action_update_form action_cancel_modal =
Web.modal modal_title modal_content modal_foot
where
modal_title = "Delegation for " <> selected_domain
modal_content :: Array (HH.HTML w i)
modal_content =
[ HH.div [HP.classes [C.notification, C.is_warning]]
[ Web.p "⚠️​ You are about to delegate your domain to another server, you won't be able to manage entries from netlibre."
]
, Web.box_input "nameserver1" "name server 1" "ns0.example.com"
(action_update_form <<< Delegation.NameServer1)
form.nameserver1
, Web.box_input "nameserver2" "name server 2" "ns1.example.com"
(action_update_form <<< Delegation.NameServer2)
form.nameserver2
]
modal_foot :: Array (HH.HTML w i)
modal_foot =
[ Web.p "should be a button here mdr"
--[ Web.btn_add action_update_form
, Web.cancel_button action_cancel_modal
]
side_text_for_name_input name_id
= Web.side_text_above_input name_id "Name" (HH.text $ "Empty name = root domain (" <> selected_domain <> ".)")
type Domain = String type Domain = String
type ActionUpdateForm i = (Field.Field -> i) type ActionUpdateForm i = (Field.Field -> i)
type ActionNewToken i = (RRId -> i) type ActionNewToken i = (RRId -> i)
@ -371,6 +401,7 @@ current_rr_modal selected_domain form rr_modal
where where
title = case rr_modal of title = case rr_modal of
NoModal -> "Error: no modal should be displayed" NoModal -> "Error: no modal should be displayed"
DelegationModal -> "Error: the delegation modal should be displayed"
NewRRModal t_ -> "New " <> show t_ <> " resource record" NewRRModal t_ -> "New " <> show t_ <> " resource record"
UpdateRRModal -> "Update " <> form._rr.rrtype <> " Resource Record" UpdateRRModal -> "Update " <> form._rr.rrtype <> " Resource Record"
RemoveRRModal rr_id -> "Error: should display removal modal instead (for resource record " <> show rr_id <> ")" RemoveRRModal rr_id -> "Error: should display removal modal instead (for resource record " <> show rr_id <> ")"

View file

@ -0,0 +1,15 @@
module App.Type.Delegation where
type Form = { nameserver1 :: String, nameserver2 :: String }
data Field
= NameServer1 String
| NameServer2 String
mkEmptyDelegationForm :: Form
mkEmptyDelegationForm = { nameserver1: "ns0.example.com", nameserver2: "ns1.example.com" }
update_delegation_field :: Form -> Field -> Form
update_delegation_field form updated_field = case updated_field of
NameServer1 val -> form { nameserver1 = val }
NameServer2 val -> form { nameserver2 = val }

View file

@ -14,3 +14,4 @@ data RRModal
| NewRRModal AcceptedRRTypes | NewRRModal AcceptedRRTypes
| UpdateRRModal | UpdateRRModal
| RemoveRRModal RRId | RemoveRRModal RRId
| DelegationModal