diff --git a/cmd/zitadel/setup.yaml b/cmd/zitadel/setup.yaml
index 7a9115c0e5..8eff6e25f4 100644
--- a/cmd/zitadel/setup.yaml
+++ b/cmd/zitadel/setup.yaml
@@ -94,87 +94,6 @@ SetUp:
Step10:
DefaultMailTemplate:
Template: PCFkb2N0eXBlIGh0bWw+CjxodG1sIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiB4bWxuczp2PSJ1cm46c2NoZW1hcy1taWNyb3NvZnQtY29tOnZtbCIgeG1sbnM6bz0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTpvZmZpY2U6b2ZmaWNlIj4KCjxoZWFkPgogICAgPHRpdGxlPiA8L3RpdGxlPgogICAgPCEtLVtpZiAhbXNvXT48IS0tIC0tPgogICAgPG1ldGEgaHR0cC1lcXVpdj0iWC1VQS1Db21wYXRpYmxlIiBjb250ZW50PSJJRT1lZGdlIj4KICAgIDwhLS08IVtlbmRpZl0tLT4KICAgIDxtZXRhIGh0dHAtZXF1aXY9IkNvbnRlbnQtVHlwZSIgY29udGVudD0idGV4dC9odG1sOyBjaGFyc2V0PVVURi04Ij4KICAgIDxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIGluaXRpYWwtc2NhbGU9MSI+CiAgICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogICAgICAgICNvdXRsb29rIGEgewogICAgICAgICAgICBwYWRkaW5nOiAwOwogICAgICAgIH0KCiAgICAgICAgYm9keSB7CiAgICAgICAgICAgIG1hcmdpbjogMDsKICAgICAgICAgICAgcGFkZGluZzogMDsKICAgICAgICAgICAgLXdlYmtpdC10ZXh0LXNpemUtYWRqdXN0OiAxMDAlOwogICAgICAgICAgICAtbXMtdGV4dC1zaXplLWFkanVzdDogMTAwJTsKICAgICAgICB9CgogICAgICAgIHRhYmxlLAogICAgICAgIHRkIHsKICAgICAgICAgICAgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTsKICAgICAgICAgICAgbXNvLXRhYmxlLWxzcGFjZTogMHB0OwogICAgICAgICAgICBtc28tdGFibGUtcnNwYWNlOiAwcHQ7CiAgICAgICAgfQoKICAgICAgICBpbWcgewogICAgICAgICAgICBib3JkZXI6IDA7CiAgICAgICAgICAgIGhlaWdodDogYXV0bzsKICAgICAgICAgICAgbGluZS1oZWlnaHQ6IDEwMCU7CiAgICAgICAgICAgIG91dGxpbmU6IG5vbmU7CiAgICAgICAgICAgIHRleHQtZGVjb3JhdGlvbjogbm9uZTsKICAgICAgICAgICAgLW1zLWludGVycG9sYXRpb24tbW9kZTogYmljdWJpYzsKICAgICAgICB9CgogICAgICAgIHAgewogICAgICAgICAgICBkaXNwbGF5OiBibG9jazsKICAgICAgICAgICAgbWFyZ2luOiAxM3B4IDA7CiAgICAgICAgfQogICAgPC9zdHlsZT4KICAgIDwhLS1baWYgbXNvXT4KICAgICAgICAgIDx4bWw+CiAgICAgICAgICA8bzpPZmZpY2VEb2N1bWVudFNldHRpbmdzPgogICAgICAgICAgICA8bzpBbGxvd1BORy8+CiAgICAgICAgICAgIDxvOlBpeGVsc1BlckluY2g+OTY8L286UGl4ZWxzUGVySW5jaD4KICAgICAgICAgIDwvbzpPZmZpY2VEb2N1bWVudFNldHRpbmdzPgogICAgICAgICAgPC94bWw+CiAgICAgICAgICA8IVtlbmRpZl0tLT4KICAgIDwhLS1baWYgbHRlIG1zbyAxMV0+CiAgICAgICAgICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogICAgICAgICAgICAubWotb3V0bG9vay1ncm91cC1maXggeyB3aWR0aDoxMDAlICFpbXBvcnRhbnQ7IH0KICAgICAgICAgIDwvc3R5bGU+CiAgICAgICAgICA8IVtlbmRpZl0tLT4KICAgIDwhLS1baWYgIW1zb10+PCEtLT4KICAgIDxsaW5rIGhyZWY9Imh0dHBzOi8vZm9udHMuZ29vZ2xlYXBpcy5jb20vY3NzP2ZhbWlseT1MYXRvOjMwMCw0MDAsNTAwLDcwMCIgcmVsPSJzdHlsZXNoZWV0IiB0eXBlPSJ0ZXh0L2NzcyI+CiAgICA8bGluayBocmVmPSJodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tL2Nzcz9mYW1pbHk9VWJ1bnR1OjMwMCw0MDAsNTAwLDcwMCIgcmVsPSJzdHlsZXNoZWV0IiB0eXBlPSJ0ZXh0L2NzcyI+CiAgICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogICAgICAgIEBpbXBvcnQgdXJsKGh0dHBzOi8vZm9udHMuZ29vZ2xlYXBpcy5jb20vY3NzP2ZhbWlseT1MYXRvOjMwMCw0MDAsNTAwLDcwMCk7CiAgICAgICAgQGltcG9ydCB1cmwoaHR0cHM6Ly9mb250cy5nb29nbGVhcGlzLmNvbS9jc3M/ZmFtaWx5PVVidW50dTozMDAsNDAwLDUwMCw3MDApOwogICAgPC9zdHlsZT4KICAgIDwhLS08IVtlbmRpZl0tLT4KICAgIDxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+CiAgICAgICAgQG1lZGlhIG9ubHkgc2NyZWVuIGFuZCAobWluLXdpZHRoOjQ4MHB4KSB7CiAgICAgICAgICAgIC5tai1jb2x1bW4tcGVyLTEwMCB7CiAgICAgICAgICAgICAgICB3aWR0aDogMTAwJSAhaW1wb3J0YW50OwogICAgICAgICAgICAgICAgbWF4LXdpZHRoOiAxMDAlOwogICAgICAgICAgICB9CiAgICAgICAgICAgIC5tai1jb2x1bW4tcGVyLTIwIHsKICAgICAgICAgICAgICAgIHdpZHRoOiAyMCUgIWltcG9ydGFudDsKICAgICAgICAgICAgICAgIG1heC13aWR0aDogMjAlOwogICAgICAgICAgICB9CiAgICAgICAgICAgIC5tai1jb2x1bW4tcGVyLTYwIHsKICAgICAgICAgICAgICAgIHdpZHRoOiA2MCUgIWltcG9ydGFudDsKICAgICAgICAgICAgICAgIG1heC13aWR0aDogNjAlOwogICAgICAgICAgICB9CiAgICAgICAgfQogICAgPC9zdHlsZT4KICAgIDxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+CiAgICAgICAgQG1lZGlhIG9ubHkgc2NyZWVuIGFuZCAobWF4LXdpZHRoOjQ4MHB4KSB7CiAgICAgICAgICAgIHRhYmxlLm1qLWZ1bGwtd2lkdGgtbW9iaWxlIHsKICAgICAgICAgICAgICAgIHdpZHRoOiAxMDAlICFpbXBvcnRhbnQ7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgdGQubWotZnVsbC13aWR0aC1tb2JpbGUgewogICAgICAgICAgICAgICAgd2lkdGg6IGF1dG8gIWltcG9ydGFudDsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogICAgICAgIEBtZWRpYSAobWF4LXdpZHRoOjQ4MHB4KSB7CiAgICAgICAgICAgIC5tb2JpbGVfaGlkZGVuIHsKICAgICAgICAgICAgICAgIGRpc3BsYXk6IG5vbmUgIWltcG9ydGFudDsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgIDwvc3R5bGU+CjwvaGVhZD4KCjxib2R5IHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOnt7LlByaW1hcnlDb2xvcn19OyI+CjxkaXYgc3R5bGU9ImJhY2tncm91bmQtY29sb3I6e3suUHJpbWFyeUNvbG9yfX07Ij4KICAgIDx0YWJsZSBhbGlnbj0iY2VudGVyIiBiYWNrZ3JvdW5kPSJodHRwczovL3N0YXRpYy56aXRhZGVsLmNoL3ppdGFkZWwtbG9nby1vdXRsaW5lLWxpZ2h0LnBuZyIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9ImJhY2tncm91bmQ6dXJsKGh0dHBzOi8vc3RhdGljLnppdGFkZWwuY2gveml0YWRlbC1sb2dvLW91dGxpbmUtbGlnaHQucG5nKSBjZW50ZXIgdG9wIC8gYXV0byBuby1yZXBlYXQ7YmFja2dyb3VuZC1wb3NpdGlvbjpjZW50ZXIgdG9wO2JhY2tncm91bmQtcmVwZWF0Om5vLXJlcGVhdDtiYWNrZ3JvdW5kLXNpemU6YXV0bzt3aWR0aDoxMDAlOyI+CiAgICAgICAgPHRib2R5PgogICAgICAgIDx0cj4KICAgICAgICAgICAgPHRkPgogICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+CiAgICAgICAgICAgIDx2OnJlY3QgIHN0eWxlPSJtc28td2lkdGgtcGVyY2VudDoxMDAwOyIgeG1sbnM6dj0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTp2bWwiIGZpbGw9InRydWUiIHN0cm9rZT0iZmFsc2UiPgogICAgICAgICAgICA8djpmaWxsICBvcmlnaW49IjAuNSwgMCIgcG9zaXRpb249IjAuNSwgMCIgc3JjPSJodHRwczovL3N0YXRpYy56aXRhZGVsLmNoL3ppdGFkZWwtbG9nby1vdXRsaW5lLWxpZ2h0LnBuZyIgdHlwZT0idGlsZSIgLz4KICAgICAgICAgICAgPHY6dGV4dGJveCBzdHlsZT0ibXNvLWZpdC1zaGFwZS10by10ZXh0OnRydWUiIGluc2V0PSIwLDAsMCwwIj4KICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIGNsYXNzPSIiIHN0eWxlPSJ3aWR0aDo4MDBweDsiIHdpZHRoPSI4MDAiCiAgICAgICAgICA+CiAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICA8dGQgc3R5bGU9ImxpbmUtaGVpZ2h0OjBweDtmb250LXNpemU6MHB4O21zby1saW5lLWhlaWdodC1ydWxlOmV4YWN0bHk7Ij4KICAgICAgICAgIDwhW2VuZGlmXS0tPgogICAgICAgICAgICAgICAgPGRpdiBzdHlsZT0ibWFyZ2luOjBweCBhdXRvO21heC13aWR0aDo4MDBweDsiPgogICAgICAgICAgICAgICAgICAgIDxkaXYgc3R5bGU9ImxpbmUtaGVpZ2h0OjA7Zm9udC1zaXplOjA7Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlIGFsaWduPSJjZW50ZXIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHN0eWxlPSJ3aWR0aDoxMDAlOyI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIHN0eWxlPSJib3JkZXI6MDtkaXJlY3Rpb246bHRyO2ZvbnQtc2l6ZTowcHg7cGFkZGluZzoyMHB4IDA7cGFkZGluZy1sZWZ0OjA7dGV4dC1hbGlnbjpjZW50ZXI7Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZSByb2xlPSJwcmVzZW50YXRpb24iIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIj4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsYXNzPSIiIHdpZHRoPSI4MDBweCIKICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICA8IVtlbmRpZl0tLT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlIGFsaWduPSJjZW50ZXIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHN0eWxlPSJ3aWR0aDoxMDAlOyI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT4KICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIGNsYXNzPSIiIHN0eWxlPSJ3aWR0aDo4MDBweDsiIHdpZHRoPSI4MDAiCiAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgc3R5bGU9ImxpbmUtaGVpZ2h0OjBweDtmb250LXNpemU6MHB4O21zby1saW5lLWhlaWdodC1ydWxlOmV4YWN0bHk7Ij4KICAgICAgICAgICAgICAgICAgICAgIDwhW2VuZGlmXS0tPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2IHN0eWxlPSJtYXJnaW46MHB4IGF1dG87bWF4LXdpZHRoOjgwMHB4OyI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IndpZHRoOjEwMCU7Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgc3R5bGU9ImRpcmVjdGlvbjpsdHI7Zm9udC1zaXplOjBweDtwYWRkaW5nOjA7dGV4dC1hbGlnbjpjZW50ZXI7Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlIHJvbGU9InByZXNlbnRhdGlvbiIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsYXNzPSIiIHN0eWxlPSJ3aWR0aDo4MDBweDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCFbZW5kaWZdLS0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJtai1jb2x1bW4tcGVyLTEwMCBtai1vdXRsb29rLWdyb3VwLWZpeCIgc3R5bGU9ImZvbnQtc2l6ZTowO2xpbmUtaGVpZ2h0OjA7dGV4dC1hbGlnbjpsZWZ0O2Rpc3BsYXk6aW5saW5lLWJsb2NrO3dpZHRoOjEwMCU7ZGlyZWN0aW9uOmx0cjsiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0eWxlPSJ2ZXJ0aWNhbC1hbGlnbjp0b3A7d2lkdGg6ODAwcHg7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IVtlbmRpZl0tLT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJtai1jb2x1bW4tcGVyLTEwMCBtai1vdXRsb29rLWdyb3VwLWZpeCIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7dGV4dC1hbGlnbjpsZWZ0O2RpcmVjdGlvbjpsdHI7ZGlzcGxheTppbmxpbmUtYmxvY2s7dmVydGljYWwtYWxpZ246dG9wO3dpZHRoOjEwMCU7Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHdpZHRoPSIxMDAlIj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgc3R5bGU9InZlcnRpY2FsLWFsaWduOnRvcDtwYWRkaW5nOjA7Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHN0eWxlPSIiIHdpZHRoPSIxMDAlIj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgYWxpZ249ImxlZnQiIHN0eWxlPSJmb250LXNpemU6MHB4O3BhZGRpbmc6MjBweCAwIDUwcHggMjBweDt3b3JkLWJyZWFrOmJyZWFrLXdvcmQ7Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHN0eWxlPSJib3JkZXItY29sbGFwc2U6Y29sbGFwc2U7Ym9yZGVyLXNwYWNpbmc6MHB4OyI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIHN0eWxlPSJ3aWR0aDoxNTBweDsiPiA8aW1nIGhlaWdodD0iYXV0byIgc3JjPSJodHRwczovL3N0YXRpYy56aXRhZGVsLmNoL3ppdGFkZWwtbG9nby1saWdodC5wbmciIHN0eWxlPSJib3JkZXI6MDtkaXNwbGF5OmJsb2NrO291dGxpbmU6bm9uZTt0ZXh0LWRlY29yYXRpb246bm9uZTtoZWlnaHQ6YXV0bzt3aWR0aDoxMDAlO2ZvbnQtc2l6ZToxM3B4OyIgd2lkdGg9IjE1MCIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLz4gPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCFbZW5kaWZdLS0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCFbZW5kaWZdLS0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPgogICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgICAgICAgPCFbZW5kaWZdLS0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT4KICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xhc3M9IiIgd2lkdGg9IjgwMHB4IgogICAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgIDwhW2VuZGlmXS0tPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IndpZHRoOjEwMCU7Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPgogICAgICAgICAgICAgICAgICAgICAgPHRhYmxlCiAgICAgICAgICAgICAgICAgICAgICAgICBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgY2xhc3M9IiIgc3R5bGU9IndpZHRoOjgwMHB4OyIgd2lkdGg9IjgwMCIKICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBzdHlsZT0ibGluZS1oZWlnaHQ6MHB4O2ZvbnQtc2l6ZTowcHg7bXNvLWxpbmUtaGVpZ2h0LXJ1bGU6ZXhhY3RseTsiPgogICAgICAgICAgICAgICAgICAgICAgPCFbZW5kaWZdLS0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgc3R5bGU9Im1hcmdpbjowcHggYXV0bzttYXgtd2lkdGg6ODAwcHg7Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZSBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0id2lkdGg6MTAwJTsiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBzdHlsZT0iZGlyZWN0aW9uOmx0cjtmb250LXNpemU6MHB4O3BhZGRpbmc6MDt0ZXh0LWFsaWduOmNlbnRlcjsiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUgcm9sZT0icHJlc2VudGF0aW9uIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xhc3M9Im1vYmlsZV9oaWRkZW4tb3V0bG9vayIgc3R5bGU9InZlcnRpY2FsLWFsaWduOnRvcDt3aWR0aDoxNjBweDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCFbZW5kaWZdLS0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJtai1jb2x1bW4tcGVyLTIwIG1qLW91dGxvb2stZ3JvdXAtZml4IG1vYmlsZV9oaWRkZW4iIHN0eWxlPSJmb250LXNpemU6MHB4O3RleHQtYWxpZ246bGVmdDtkaXJlY3Rpb246bHRyO2Rpc3BsYXk6aW5saW5lLWJsb2NrO3ZlcnRpY2FsLWFsaWduOnRvcDt3aWR0aDoxMDAlOyI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHdpZHRoPSIxMDAlIj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgc3R5bGU9InZlcnRpY2FsLWFsaWduOnRvcDtwYWRkaW5nOjA7Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IiIgd2lkdGg9IjEwMCUiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgYWxpZ249ImxlZnQiIHN0eWxlPSJmb250LXNpemU6MHB4O3BhZGRpbmc6MDt3b3JkLWJyZWFrOmJyZWFrLXdvcmQ7Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9ImJvcmRlci1jb2xsYXBzZTpjb2xsYXBzZTtib3JkZXItc3BhY2luZzowcHg7Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgc3R5bGU9IndpZHRoOjgwcHg7Ij4gPGltZyBoZWlnaHQ9IjEwMCIgc3JjPSJodHRwczovL3N0YXRpYy56aXRhZGVsLmNoL2ZsYXZvci1zcGlrZXMtc21hbGwtb3BhY2l0eTQwLnBuZyIgc3R5bGU9ImJvcmRlcjowO2Rpc3BsYXk6YmxvY2s7b3V0bGluZTpub25lO3RleHQtZGVjb3JhdGlvbjpub25lO2hlaWdodDoxMDAlO3dpZHRoOjEwMCU7Zm9udC1zaXplOjEzcHg7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lkdGg9IjgwIiAvPiA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xhc3M9IiIgc3R5bGU9InZlcnRpY2FsLWFsaWduOnRvcDt3aWR0aDo0ODBweDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCFbZW5kaWZdLS0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJtai1jb2x1bW4tcGVyLTYwIG1qLW91dGxvb2stZ3JvdXAtZml4IiBzdHlsZT0iZm9udC1zaXplOjBweDt0ZXh0LWFsaWduOmxlZnQ7ZGlyZWN0aW9uOmx0cjtkaXNwbGF5OmlubGluZS1ibG9jazt2ZXJ0aWNhbC1hbGlnbjp0b3A7d2lkdGg6MTAwJTsiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZSBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiB3aWR0aD0iMTAwJSI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIHN0eWxlPSJ2ZXJ0aWNhbC1hbGlnbjp0b3A7cGFkZGluZzowOyI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHN0eWxlPSIiIHdpZHRoPSIxMDAlIj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIGFsaWduPSJjZW50ZXIiIHN0eWxlPSJmb250LXNpemU6MHB4O3BhZGRpbmc6MTBweCAyNXB4O3dvcmQtYnJlYWs6YnJlYWstd29yZDsiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgc3R5bGU9ImZvbnQtZmFtaWx5OkxhdG8sIEFyaWFsLCBIZWx2ZXRpY2EsIHNhbnMtc2VyaWY7Zm9udC1zaXplOjJyZW07Zm9udC13ZWlnaHQ6MjAwO2xpbmUtaGVpZ2h0OjE7dGV4dC1hbGlnbjpjZW50ZXI7Y29sb3I6e3suU2Vjb25kYXJ5Q29sb3J9fTsiPnt7LkdyZWV0aW5nfX08L2Rpdj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBhbGlnbj0iY2VudGVyIiBzdHlsZT0iZm9udC1zaXplOjBweDtwYWRkaW5nOjEwcHggMjVweDt3b3JkLWJyZWFrOmJyZWFrLXdvcmQ7Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2IHN0eWxlPSJmb250LWZhbWlseTpMYXRvLCBBcmlhbCwgSGVsdmV0aWNhLCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxcmVtO2ZvbnQtd2VpZ2h0OmxpZ2h0O2xpbmUtaGVpZ2h0OjEuNTt0ZXh0LWFsaWduOmNlbnRlcjtjb2xvcjp7ey5TZWNvbmRhcnlDb2xvcn19OyI+e3suVGV4dH19PC9kaXY+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgYWxpZ249ImNlbnRlciIgdmVydGljYWwtYWxpZ249Im1pZGRsZSIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxMHB4IDI1cHg7d29yZC1icmVhazpicmVhay13b3JkOyI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHN0eWxlPSJib3JkZXItY29sbGFwc2U6c2VwYXJhdGU7bGluZS1oZWlnaHQ6MTAwJTsiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgYWxpZ249ImNlbnRlciIgYmdjb2xvcj0iIzUyODJDMSIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iYm9yZGVyOm5vbmU7Ym9yZGVyLXJhZGl1czozcHg7Y3Vyc29yOmF1dG87bXNvLXBhZGRpbmctYWx0OjEwcHggMjVweDtiYWNrZ3JvdW5kOiM1MjgyQzE7IiB2YWxpZ249Im1pZGRsZSI+IDxhIGhyZWY9Int7LlVSTH19IiBzdHlsZT0iZGlzcGxheTppbmxpbmUtYmxvY2s7YmFja2dyb3VuZDojNTI4MkMxO2NvbG9yOiNmZmZmZmY7Zm9udC1mYW1pbHk6VWJ1bnR1LCBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmO2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0Om5vcm1hbDtsaW5lLWhlaWdodDoxMjAlO21hcmdpbjowO3RleHQtZGVjb3JhdGlvbjpub25lO3RleHQtdHJhbnNmb3JtOm5vbmU7cGFkZGluZzoxMHB4IDI1cHg7bXNvLXBhZGRpbmctYWx0OjBweDtib3JkZXItcmFkaXVzOjNweDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhcmdldD0iX2JsYW5rIj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAge3suQnV0dG9uVGV4dH19CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9hPiA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgYWxpZ249ImNlbnRlciIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzozMHB4IDA7d29yZC1icmVhazpicmVhay13b3JkOyI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdiBzdHlsZT0iZm9udC1mYW1pbHk6TGF0bywgQXJpYWwsIEhlbHZldGljYSwgc2Fucy1zZXJpZjtmb250LXNpemU6MTNweDtsaW5lLWhlaWdodDoxO3RleHQtYWxpZ246Y2VudGVyO2NvbG9yOnt7LlNlY29uZGFyeUNvbG9yfX07Ij48YSBocmVmPSJodHRwOi8vd3d3LmNhb3MuY2giIHN0eWxlPSJjb2xvcjojZTkxZTYzOyB0ZXh0LWRlY29yYXRpb246IG5vbmU7IiB0YXJnZXQ9Il9ibGFuayI+IENBT1MgQUcgPC9hPiB8IFRldWZlbmVyIFN0cmFzc2UgMTkgfCBDSC05MDAwIFN0LiBHYWxsZW48L2Rpdj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzcz0ibW9iaWxlX2hpZGRlbi1vdXRsb29rIiBzdHlsZT0idmVydGljYWwtYWxpZ246dG9wO3dpZHRoOjE2MHB4OyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IVtlbmRpZl0tLT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9Im1qLWNvbHVtbi1wZXItMjAgbWotb3V0bG9vay1ncm91cC1maXggbW9iaWxlX2hpZGRlbiIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7dGV4dC1hbGlnbjpsZWZ0O2RpcmVjdGlvbjpsdHI7ZGlzcGxheTppbmxpbmUtYmxvY2s7dmVydGljYWwtYWxpZ246dG9wO3dpZHRoOjEwMCU7Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgd2lkdGg9IjEwMCUiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBzdHlsZT0idmVydGljYWwtYWxpZ246dG9wO3BhZGRpbmc6MDsiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZSBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iIiB3aWR0aD0iMTAwJSI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBhbGlnbj0icmlnaHQiIHN0eWxlPSJmb250LXNpemU6MHB4O3BhZGRpbmc6MDt3b3JkLWJyZWFrOmJyZWFrLXdvcmQ7Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9ImJvcmRlci1jb2xsYXBzZTpjb2xsYXBzZTtib3JkZXItc3BhY2luZzowcHg7Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgc3R5bGU9IndpZHRoOjE2MHB4OyI+IDxpbWcgaGVpZ2h0PSJhdXRvIiBzcmM9Imh0dHBzOi8vc3RhdGljLnppdGFkZWwuY2gvZmxhdm9yLXNwaWtlcy1iaWctb3BhY2l0eTQwLnBuZyIgc3R5bGU9ImJvcmRlcjowO2Rpc3BsYXk6YmxvY2s7b3V0bGluZTpub25lO3RleHQtZGVjb3JhdGlvbjpub25lO2hlaWdodDphdXRvO3dpZHRoOjEwMCU7Zm9udC1zaXplOjEzcHg7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdpZHRoPSIxNjAiIC8+IDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IVtlbmRpZl0tLT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICAgICAgICA8IVtlbmRpZl0tLT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPgogICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzcz0iIiB3aWR0aD0iODAwcHgiCiAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgPCFbZW5kaWZdLS0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZSBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0id2lkdGg6MTAwJTsiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+CiAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduPSJjZW50ZXIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiBjbGFzcz0iIiBzdHlsZT0id2lkdGg6ODAwcHg7IiB3aWR0aD0iODAwIgogICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIHN0eWxlPSJsaW5lLWhlaWdodDowcHg7Zm9udC1zaXplOjBweDttc28tbGluZS1oZWlnaHQtcnVsZTpleGFjdGx5OyI+CiAgICAgICAgICAgICAgICAgICAgICA8IVtlbmRpZl0tLT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdiBzdHlsZT0ibWFyZ2luOjBweCBhdXRvO21heC13aWR0aDo4MDBweDsiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlIGFsaWduPSJjZW50ZXIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHN0eWxlPSJ3aWR0aDoxMDAlOyI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIHN0eWxlPSJkaXJlY3Rpb246bHRyO2ZvbnQtc2l6ZTowcHg7cGFkZGluZzowO3RleHQtYWxpZ246Y2VudGVyOyI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZSByb2xlPSJwcmVzZW50YXRpb24iIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzcz0iIiBzdHlsZT0id2lkdGg6ODAwcHg7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwhW2VuZGlmXS0tPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibWotY29sdW1uLXBlci0xMDAgbWotb3V0bG9vay1ncm91cC1maXgiIHN0eWxlPSJmb250LXNpemU6MDtsaW5lLWhlaWdodDowO3RleHQtYWxpZ246bGVmdDtkaXNwbGF5OmlubGluZS1ibG9jazt3aWR0aDoxMDAlO2RpcmVjdGlvbjpsdHI7Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0idmVydGljYWwtYWxpZ246dG9wO3dpZHRoOjgwMHB4OyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCFbZW5kaWZdLS0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibWotY29sdW1uLXBlci0xMDAgbWotb3V0bG9vay1ncm91cC1maXgiIHN0eWxlPSJmb250LXNpemU6MHB4O3RleHQtYWxpZ246bGVmdDtkaXJlY3Rpb246bHRyO2Rpc3BsYXk6aW5saW5lLWJsb2NrO3ZlcnRpY2FsLWFsaWduOnRvcDt3aWR0aDoxMDAlOyI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZSBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiB3aWR0aD0iMTAwJSI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIHN0eWxlPSJ2ZXJ0aWNhbC1hbGlnbjp0b3A7cGFkZGluZzoyMHB4OyI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZSBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iIiB3aWR0aD0iMTAwJSI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIGFsaWduPSJyaWdodCIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzowO3dvcmQtYnJlYWs6YnJlYWstd29yZDsiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9ImJvcmRlci1jb2xsYXBzZTpjb2xsYXBzZTtib3JkZXItc3BhY2luZzowcHg7Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgc3R5bGU9IndpZHRoOjY1cHg7Ij4gPGltZyBoZWlnaHQ9ImF1dG8iIHNyYz0iaHR0cHM6Ly9zdGF0aWMueml0YWRlbC5jaC9sb2dvX3doaXRlZm9udF90cmFuc3BhcmVudGJnLnBuZyIgc3R5bGU9ImJvcmRlcjowO2Rpc3BsYXk6YmxvY2s7b3V0bGluZTpub25lO3RleHQtZGVjb3JhdGlvbjpub25lO2hlaWdodDphdXRvO3dpZHRoOjEwMCU7Zm9udC1zaXplOjEzcHg7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdpZHRoPSI2NSIgLz4gPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCFbZW5kaWZdLS0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCFbZW5kaWZdLS0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPgogICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgICAgICAgPCFbZW5kaWZdLS0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT4KICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPCFbZW5kaWZdLS0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT4KICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICA8L3RyPgogICAgICAgICAgPC90YWJsZT4KICAgICAgICAgICAgPC92OnRleHRib3g+CiAgICAgICAgICA8L3Y6cmVjdD4KICAgICAgICA8IVtlbmRpZl0tLT4KICAgICAgICAgICAgPC90ZD4KICAgICAgICA8L3RyPgogICAgICAgIDwvdGJvZHk+CiAgICA8L3RhYmxlPgo8L2Rpdj4KPC9ib2R5PgoKPC9odG1sPg==
- DefaultMailTexts:
- - MailTextType: InitCode
- Language: DE
- Title: Zitadel - User initialisieren
- PreHeader: User initialisieren
- Subject: User initialisieren
- Greeting: Hallo {{.FirstName}} {{.LastName}},
- Text: Dieser Benutzer wurde soeben im Zitadel erstellt. Mit dem Benutzernamen <br><strong>{{.PreferredLoginName}}</strong><br> kannst du dich anmelden. Nutze den untenstehenden Button, um die Initialisierung abzuschliessen <br>(Code <strong>{{.Code}}</strong>).<br> Falls du dieses Mail nicht angefordert hast, kannst du es einfach ignorieren.
- ButtonText: Initialisierung abschliessen
- - MailTextType: PasswordReset
- Language: DE
- Title: Zitadel - Passwort zurücksetzen
- PreHeader: Passwort zurücksetzen
- Subject: Passwort zurücksetzen
- Greeting: Hallo {{.FirstName}} {{.LastName}},
- Text: Wir haben eine Anfrage für das Zurücksetzen deines Passwortes bekommen. Du kannst den untenstehenden Button verwenden, um dein Passwort zurückzusetzen <br>(Code <strong>{{.Code}}</strong>).<br> Falls du dieses Mail nicht angefordert hast, kannst du es ignorieren.
- ButtonText: Passwort zurücksetzen
- - MailTextType: VerifyEmail
- Language: DE
- Title: Zitadel - Email verifizieren
- PreHeader: Email verifizieren
- Subject: Email verifizieren
- Greeting: Hallo {{.FirstName}} {{.LastName}},
- Text: Eine neue E-Mail Adresse wurde hinzugefügt. Bitte verwende den untenstehenden Button um diese zu verifizieren <br>(Code <strong>{{.Code}}</strong>).<br> Falls du deine E-Mail Adresse nicht selber hinzugefügt hast, kannst du dieses E-Mail ignorieren.
- ButtonText: Email verifizieren
- - MailTextType: VerifyPhone
- Language: DE
- Title: Zitadel - Telefonnummer verifizieren
- PreHeader: Telefonnummer verifizieren
- Subject: Telefonnummer verifizieren
- Greeting: Hallo {{.FirstName}} {{.LastName}},
- Text: Eine Telefonnummer wurde hinzugefügt. Bitte verifiziere diese in dem du folgenden Code eingibst<br>(Code <strong>{{.Code}}</strong>).<br>
- ButtonText: Telefon verifizieren
- - MailTextType: DomainClaimed
- Language: DE
- Title: Zitadel - Domain wurde beansprucht
- PreHeader: Email / Username ändern
- Subject: Domain wurde beansprucht
- Greeting: Hallo {{.FirstName}} {{.LastName}},
- Text: Die Domain {{.Domain}} wurde von einer Organisation beansprucht. Dein derzeitiger User {{.Username}} ist nicht Teil dieser Organisation. Daher musst du beim nächsten Login eine neue Email hinterlegen. Für diesen Login haben wir dir einen temporären Usernamen ({{.TempUsername}}) erstellt.
- ButtonText: Login
- - MailTextType: InitCode
- Language: EN
- Title: Zitadel - Initialize User
- PreHeader: Initialize User
- Subject: Initialize User
- Greeting: Hello {{.FirstName}} {{.LastName}},
- Text: This user was created in Zitadel. Use the username {{.PreferredLoginName}} to login. Please click the button below to finish the initialization process. (Code {{.Code}}) If you didn't ask for this mail, please ignore it.
- ButtonText: Finish initialization
- - MailTextType: PasswordReset
- Language: EN
- Title: Zitadel - Reset password
- PreHeader: Reset password
- Subject: Reset password
- Greeting: Hello {{.FirstName}} {{.LastName}},
- Text: We received a password reset request. Please use the button below to reset your password. (Code {{.Code}}) If you didn't ask for this mail, please ignore it.
- ButtonText: Reset password
- - MailTextType: VerifyEmail
- Language: EN
- Title: Zitadel - Verify email
- PreHeader: Verify email
- Subject: Verify email
- Greeting: Hello {{.FirstName}} {{.LastName}},
- Text: A new email has been added. Please use the button below to verify your mail. (Code {{.Code}}) If you din't add a new email, please ignore this email.
- ButtonText: Verify email
- - MailTextType: VerifyPhone
- Language: EN
- Title: Zitadel - Verify phone
- PreHeader: Verify phone
- Subject: Verify phone
- Greeting: Hello {{.FirstName}} {{.LastName}},
- Text: A new phonenumber has been added. Please use the following code to verify it {{.Code}}.
- ButtonText: Verify phone
- - MailTextType: DomainClaimed
- Language: EN
- Title: Zitadel - Domain has been claimed
- PreHeader: Change email / username
- Subject: Domain has been claimed
- Greeting: Hello {{.FirstName}} {{.LastName}},
- Text: The domain {{.Domain}} has been claimed by an organisation. Your current user {{.Username}} is not part of this organisation. Therefore you'll have to change your email when you login. We have created a temporary username ({{.TempUsername}}) for this login.
- ButtonText: Login
Step11:
MigrateV1EventstoreToV2: $ZITADEL_MIGRATE_ES_V1
Step12:
@@ -188,3 +107,85 @@ SetUp:
Step15:
DefaultMailTemplate:
Template: CjwhZG9jdHlwZSBodG1sPgo8aHRtbCB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgeG1sbnM6dj0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTp2bWwiIHhtbG5zOm89InVybjpzY2hlbWFzLW1pY3Jvc29mdC1jb206b2ZmaWNlOm9mZmljZSI+CjxoZWFkPgogIDx0aXRsZT4KCiAgPC90aXRsZT4KICA8IS0tW2lmICFtc29dPjwhLS0+CiAgPG1ldGEgaHR0cC1lcXVpdj0iWC1VQS1Db21wYXRpYmxlIiBjb250ZW50PSJJRT1lZGdlIj4KICA8IS0tPCFbZW5kaWZdLS0+CiAgPG1ldGEgaHR0cC1lcXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJzZXQ9VVRGLTgiPgogIDxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIGluaXRpYWwtc2NhbGU9MSI+CiAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KICAgICNvdXRsb29rIGEgeyBwYWRkaW5nOjA7IH0KICAgIGJvZHkgeyBtYXJnaW46MDtwYWRkaW5nOjA7LXdlYmtpdC10ZXh0LXNpemUtYWRqdXN0OjEwMCU7LW1zLXRleHQtc2l6ZS1hZGp1c3Q6MTAwJTsgfQogICAgdGFibGUsIHRkIHsgYm9yZGVyLWNvbGxhcHNlOmNvbGxhcHNlO21zby10YWJsZS1sc3BhY2U6MHB0O21zby10YWJsZS1yc3BhY2U6MHB0OyB9CiAgICBpbWcgeyBib3JkZXI6MDtoZWlnaHQ6YXV0bztsaW5lLWhlaWdodDoxMDAlOyBvdXRsaW5lOm5vbmU7dGV4dC1kZWNvcmF0aW9uOm5vbmU7LW1zLWludGVycG9sYXRpb24tbW9kZTpiaWN1YmljOyB9CiAgICBwIHsgZGlzcGxheTpibG9jazttYXJnaW46MTNweCAwOyB9CiAgPC9zdHlsZT4KICA8IS0tW2lmIG1zb10+CiAgPHhtbD4KICAgIDxvOk9mZmljZURvY3VtZW50U2V0dGluZ3M+CiAgICAgIDxvOkFsbG93UE5HLz4KICAgICAgPG86UGl4ZWxzUGVySW5jaD45NjwvbzpQaXhlbHNQZXJJbmNoPgogICAgPC9vOk9mZmljZURvY3VtZW50U2V0dGluZ3M+CiAgPC94bWw+CiAgPCFbZW5kaWZdLS0+CiAgPCEtLVtpZiBsdGUgbXNvIDExXT4KICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogICAgLm1qLW91dGxvb2stZ3JvdXAtZml4IHsgd2lkdGg6MTAwJSAhaW1wb3J0YW50OyB9CiAgPC9zdHlsZT4KICA8IVtlbmRpZl0tLT4KCiAgPCEtLVtpZiAhbXNvXT48IS0tPgogIDxsaW5rIGhyZWY9Imh0dHBzOi8vZm9udHMuZ29vZ2xlYXBpcy5jb20vY3NzP2ZhbWlseT1VYnVudHU6MzAwLDQwMCw1MDAsNzAwIiByZWw9InN0eWxlc2hlZXQiIHR5cGU9InRleHQvY3NzIj4KICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogICAgQGltcG9ydCB1cmwoaHR0cHM6Ly9mb250cy5nb29nbGVhcGlzLmNvbS9jc3M/ZmFtaWx5PVVidW50dTozMDAsNDAwLDUwMCw3MDApOwogIDwvc3R5bGU+CiAgPCEtLTwhW2VuZGlmXS0tPgoKCgogIDxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+CiAgICBAbWVkaWEgb25seSBzY3JlZW4gYW5kIChtaW4td2lkdGg6NDgwcHgpIHsKICAgICAgLm1qLWNvbHVtbi1wZXItMTAwIHsgd2lkdGg6MTAwJSAhaW1wb3J0YW50OyBtYXgtd2lkdGg6IDEwMCU7IH0KICAgICAgLm1qLWNvbHVtbi1wZXItNjAgeyB3aWR0aDo2MCUgIWltcG9ydGFudDsgbWF4LXdpZHRoOiA2MCU7IH0KICAgIH0KICA8L3N0eWxlPgoKCiAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCgoKICAgIEBtZWRpYSBvbmx5IHNjcmVlbiBhbmQgKG1heC13aWR0aDo0ODBweCkgewogICAgICB0YWJsZS5tai1mdWxsLXdpZHRoLW1vYmlsZSB7IHdpZHRoOiAxMDAlICFpbXBvcnRhbnQ7IH0KICAgICAgdGQubWotZnVsbC13aWR0aC1tb2JpbGUgeyB3aWR0aDogYXV0byAhaW1wb3J0YW50OyB9CiAgICB9CgogIDwvc3R5bGU+CiAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4uc2hhZG93IGEgewogICAgYm94LXNoYWRvdzogMHB4IDNweCAxcHggLTJweCByZ2JhKDAsIDAsIDAsIDAuMiksIDBweCAycHggMnB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLCAwcHggMXB4IDVweCAwcHggcmdiYSgwLCAwLCAwLCAwLjEyKTsKICB9PC9zdHlsZT4KCiAge3tpZiAuRm9udFVSTH19CiAgPHN0eWxlPgogICAgQGZvbnQtZmFjZSB7CiAgICAgIGZvbnQtZmFtaWx5OiAne3suRm9udEZhbWlseX19JzsKICAgICAgZm9udC1zdHlsZTogbm9ybWFsOwogICAgICBmb250LWRpc3BsYXk6IHN3YXA7CiAgICAgIHNyYzogdXJsKHt7LkZvbnRVUkx9fSk7CiAgICB9CiAgPC9zdHlsZT4KICB7e2VuZH19Cgo8L2hlYWQ+Cjxib2R5IHN0eWxlPSJ3b3JkLXNwYWNpbmc6bm9ybWFsOyI+CgoKPGRpdgogICAgICAgIHN0eWxlPSIiCj4KCiAgPHRhYmxlCiAgICAgICAgICBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iYmFja2dyb3VuZDp7ey5CYWNrZ3JvdW5kQ29sb3J9fTtiYWNrZ3JvdW5kLWNvbG9yOnt7LkJhY2tncm91bmRDb2xvcn19O3dpZHRoOjEwMCU7Ym9yZGVyLXJhZGl1czoxNnB4OyIKICA+CiAgICA8dGJvZHk+CiAgICA8dHI+CiAgICAgIDx0ZD4KCgogICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgY2xhc3M9IiIgc3R5bGU9IndpZHRoOjgwMHB4OyIgd2lkdGg9IjgwMCIgPjx0cj48dGQgc3R5bGU9ImxpbmUtaGVpZ2h0OjBweDtmb250LXNpemU6MHB4O21zby1saW5lLWhlaWdodC1ydWxlOmV4YWN0bHk7Ij48IVtlbmRpZl0tLT4KCgogICAgICAgIDxkaXYgIHN0eWxlPSJtYXJnaW46MHB4IGF1dG87Ym9yZGVyLXJhZGl1czoxNnB4O21heC13aWR0aDo4MDBweDsiPgoKICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0id2lkdGg6MTAwJTtib3JkZXItcmFkaXVzOjE2cHg7IgogICAgICAgICAgPgogICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgIHN0eWxlPSJkaXJlY3Rpb246bHRyO2ZvbnQtc2l6ZTowcHg7cGFkZGluZzoyMHB4IDA7cGFkZGluZy1sZWZ0OjA7dGV4dC1hbGlnbjpjZW50ZXI7IgogICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSByb2xlPSJwcmVzZW50YXRpb24iIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIj48dHI+PHRkIGNsYXNzPSIiIHdpZHRoPSI4MDBweCIgPjwhW2VuZGlmXS0tPgoKICAgICAgICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICAgICAgICBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0id2lkdGg6MTAwJTsiCiAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD4KCgogICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+PHRhYmxlIGFsaWduPSJjZW50ZXIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiBjbGFzcz0iIiBzdHlsZT0id2lkdGg6ODAwcHg7IiB3aWR0aD0iODAwIiA+PHRyPjx0ZCBzdHlsZT0ibGluZS1oZWlnaHQ6MHB4O2ZvbnQtc2l6ZTowcHg7bXNvLWxpbmUtaGVpZ2h0LXJ1bGU6ZXhhY3RseTsiPjwhW2VuZGlmXS0tPgoKCiAgICAgICAgICAgICAgICAgICAgICA8ZGl2ICBzdHlsZT0ibWFyZ2luOjBweCBhdXRvO21heC13aWR0aDo4MDBweDsiPgoKICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IndpZHRoOjEwMCU7IgogICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0iZGlyZWN0aW9uOmx0cjtmb250LXNpemU6MHB4O3BhZGRpbmc6MDt0ZXh0LWFsaWduOmNlbnRlcjsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSByb2xlPSJwcmVzZW50YXRpb24iIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIj48dHI+PHRkIGNsYXNzPSIiIHN0eWxlPSJ3aWR0aDo4MDBweDsiID48IVtlbmRpZl0tLT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzcz0ibWotY29sdW1uLXBlci0xMDAgbWotb3V0bG9vay1ncm91cC1maXgiIHN0eWxlPSJmb250LXNpemU6MDtsaW5lLWhlaWdodDowO3RleHQtYWxpZ246bGVmdDtkaXNwbGF5OmlubGluZS1ibG9jazt3aWR0aDoxMDAlO2RpcmVjdGlvbjpsdHI7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+PHRhYmxlIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iID48dHI+PHRkIHN0eWxlPSJ2ZXJ0aWNhbC1hbGlnbjp0b3A7d2lkdGg6ODAwcHg7IiA+PCFbZW5kaWZdLS0+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsYXNzPSJtai1jb2x1bW4tcGVyLTEwMCBtai1vdXRsb29rLWdyb3VwLWZpeCIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7dGV4dC1hbGlnbjpsZWZ0O2RpcmVjdGlvbjpsdHI7ZGlzcGxheTppbmxpbmUtYmxvY2s7dmVydGljYWwtYWxpZ246dG9wO3dpZHRoOjEwMCU7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgd2lkdGg9IjEwMCUiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCAgc3R5bGU9InZlcnRpY2FsLWFsaWduOnRvcDtwYWRkaW5nOjA7Ij4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IiIgd2lkdGg9IjEwMCUiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduPSJjZW50ZXIiIHN0eWxlPSJmb250LXNpemU6MHB4O3BhZGRpbmc6NTBweCAwIDMwcHggMDt3b3JkLWJyZWFrOmJyZWFrLXdvcmQ7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9ImJvcmRlci1jb2xsYXBzZTpjb2xsYXBzZTtib3JkZXItc3BhY2luZzowcHg7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgIHN0eWxlPSJ3aWR0aDoxODBweDsiPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxpbWcKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGVpZ2h0PSJhdXRvIiBzcmM9Int7LkxvZ29VUkx9fSIgc3R5bGU9ImJvcmRlcjowO2JvcmRlci1yYWRpdXM6OHB4O2Rpc3BsYXk6YmxvY2s7b3V0bGluZTpub25lO3RleHQtZGVjb3JhdGlvbjpub25lO2hlaWdodDphdXRvO3dpZHRoOjEwMCU7Zm9udC1zaXplOjEzcHg7IiB3aWR0aD0iMTgwIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLz4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgoKCiAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KCgogICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjwvdGQ+PC90cj48dHI+PHRkIGNsYXNzPSIiIHdpZHRoPSI4MDBweCIgPjwhW2VuZGlmXS0tPgoKICAgICAgICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICAgICAgICBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0id2lkdGg6MTAwJTsiCiAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgIDx0ZD4KCgogICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+PHRhYmxlIGFsaWduPSJjZW50ZXIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiBjbGFzcz0iIiBzdHlsZT0id2lkdGg6ODAwcHg7IiB3aWR0aD0iODAwIiA+PHRyPjx0ZCBzdHlsZT0ibGluZS1oZWlnaHQ6MHB4O2ZvbnQtc2l6ZTowcHg7bXNvLWxpbmUtaGVpZ2h0LXJ1bGU6ZXhhY3RseTsiPjwhW2VuZGlmXS0tPgoKCiAgICAgICAgICAgICAgICAgICAgICA8ZGl2ICBzdHlsZT0ibWFyZ2luOjBweCBhdXRvO21heC13aWR0aDo4MDBweDsiPgoKICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IndpZHRoOjEwMCU7IgogICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0iZGlyZWN0aW9uOmx0cjtmb250LXNpemU6MHB4O3BhZGRpbmc6MDt0ZXh0LWFsaWduOmNlbnRlcjsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSByb2xlPSJwcmVzZW50YXRpb24iIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIj48dHI+PHRkIGNsYXNzPSIiIHN0eWxlPSJ2ZXJ0aWNhbC1hbGlnbjp0b3A7d2lkdGg6NDgwcHg7IiA+PCFbZW5kaWZdLS0+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xhc3M9Im1qLWNvbHVtbi1wZXItNjAgbWotb3V0bG9vay1ncm91cC1maXgiIHN0eWxlPSJmb250LXNpemU6MHB4O3RleHQtYWxpZ246bGVmdDtkaXJlY3Rpb246bHRyO2Rpc3BsYXk6aW5saW5lLWJsb2NrO3ZlcnRpY2FsLWFsaWduOnRvcDt3aWR0aDoxMDAlOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHdpZHRoPSIxMDAlIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkICBzdHlsZT0idmVydGljYWwtYWxpZ246dG9wO3BhZGRpbmc6MDsiPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHN0eWxlPSIiIHdpZHRoPSIxMDAlIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduPSJjZW50ZXIiIHN0eWxlPSJmb250LXNpemU6MHB4O3BhZGRpbmc6MTBweCAyNXB4O3dvcmQtYnJlYWs6YnJlYWstd29yZDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGU9ImZvbnQtZmFtaWx5Ont7LkZvbnRGYW1pbHl9fTtmb250LXNpemU6MjRweDtmb250LXdlaWdodDo1MDA7bGluZS1oZWlnaHQ6MTt0ZXh0LWFsaWduOmNlbnRlcjtjb2xvcjp7ey5Gb250Q29sb3J9fTsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPnt7LkdyZWV0aW5nfX08L2Rpdj4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxMHB4IDI1cHg7d29yZC1icmVhazpicmVhay13b3JkOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0iZm9udC1mYW1pbHk6e3suRm9udEZhbWlseX19O2ZvbnQtc2l6ZToxNnB4O2ZvbnQtd2VpZ2h0OmxpZ2h0O2xpbmUtaGVpZ2h0OjEuNTt0ZXh0LWFsaWduOmNlbnRlcjtjb2xvcjp7ey5Gb250Q29sb3J9fTsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPnt7LlRleHR9fTwvZGl2PgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgdmVydGljYWwtYWxpZ249Im1pZGRsZSIgY2xhc3M9InNoYWRvdyIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxMHB4IDI1cHg7d29yZC1icmVhazpicmVhay13b3JkOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHN0eWxlPSJib3JkZXItY29sbGFwc2U6c2VwYXJhdGU7bGluZS1oZWlnaHQ6MTAwJTsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbGlnbj0iY2VudGVyIiBiZ2NvbG9yPSJ7ey5QcmltYXJ5Q29sb3J9fSIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iYm9yZGVyOm5vbmU7Ym9yZGVyLXJhZGl1czo2cHg7Y3Vyc29yOmF1dG87bXNvLXBhZGRpbmctYWx0OjEwcHggMjVweDtiYWNrZ3JvdW5kOnt7LlByaW1hcnlDb2xvcn19OyIgdmFsaWduPSJtaWRkbGUiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8YQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaHJlZj0ie3suVVJMfX0iIHJlbD0ibm9vcGVuZXIgbm9yZWZlcnJlciIgc3R5bGU9ImRpc3BsYXk6aW5saW5lLWJsb2NrO2JhY2tncm91bmQ6e3suUHJpbWFyeUNvbG9yfX07Y29sb3I6I2ZmZmZmZjtmb250LWZhbWlseTpVYnVudHUsIEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7Zm9udC1zaXplOjE0cHg7Zm9udC13ZWlnaHQ6NTAwO2xpbmUtaGVpZ2h0OjEyMCU7bWFyZ2luOjA7dGV4dC1kZWNvcmF0aW9uOm5vbmU7dGV4dC10cmFuc2Zvcm06bm9uZTtwYWRkaW5nOjEwcHggMjVweDttc28tcGFkZGluZy1hbHQ6MHB4O2JvcmRlci1yYWRpdXM6NnB4OyIgdGFyZ2V0PSJfYmxhbmsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAge3suQnV0dG9uVGV4dH19CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9hPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7e2lmIC5JbmNsdWRlRm9vdGVyfX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxMHB4IDI1cHg7cGFkZGluZy10b3A6MjBweDtwYWRkaW5nLXJpZ2h0OjIwcHg7cGFkZGluZy1ib3R0b206MjBweDtwYWRkaW5nLWxlZnQ6MjBweDt3b3JkLWJyZWFrOmJyZWFrLXdvcmQ7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0iYm9yZGVyLXRvcDpzb2xpZCAycHggI2RiZGJkYjtmb250LXNpemU6MXB4O21hcmdpbjowcHggYXV0bzt3aWR0aDoxMDAlOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9wPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48dGFibGUgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHN0eWxlPSJib3JkZXItdG9wOnNvbGlkIDJweCAjZGJkYmRiO2ZvbnQtc2l6ZToxcHg7bWFyZ2luOjBweCBhdXRvO3dpZHRoOjQ0MHB4OyIgcm9sZT0icHJlc2VudGF0aW9uIiB3aWR0aD0iNDQwcHgiID48dHI+PHRkIHN0eWxlPSJoZWlnaHQ6MDtsaW5lLWhlaWdodDowOyI+ICZuYnNwOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+PC90cj48L3RhYmxlPjwhW2VuZGlmXS0tPgoKCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxNnB4O3dvcmQtYnJlYWs6YnJlYWstd29yZDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGU9ImZvbnQtZmFtaWx5Ont7LkZvbnRGYW1pbHl9fTtmb250LXNpemU6MTNweDtsaW5lLWhlaWdodDoxO3RleHQtYWxpZ246Y2VudGVyO2NvbG9yOnt7LkZvbnRDb2xvcn19OyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+e3suRm9vdGVyVGV4dH19PC9kaXY+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHt7ZW5kfX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgoKCiAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KCgogICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjwvdGQ+PC90cj48L3RhYmxlPjwhW2VuZGlmXS0tPgogICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICA8L2Rpdj4KCgogICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjwvdGQ+PC90cj48L3RhYmxlPjwhW2VuZGlmXS0tPgoKCiAgICAgIDwvdGQ+CiAgICA8L3RyPgogICAgPC90Ym9keT4KICA8L3RhYmxlPgoKPC9kaXY+Cgo8L2JvZHk+CjwvaHRtbD4KICA=
+ Step16:
+ DefaultMessageTexts:
+ - MessageTextType: InitCode
+ Language: de
+ Title: Zitadel - User initialisieren
+ PreHeader: User initialisieren
+ Subject: User initialisieren
+ Greeting: Hallo {{.FirstName}} {{.LastName}},
+ Text: Dieser Benutzer wurde soeben im Zitadel erstellt. Mit dem Benutzernamen <br><strong>{{.PreferredLoginName}}</strong><br> kannst du dich anmelden. Nutze den untenstehenden Button, um die Initialisierung abzuschliessen <br>(Code <strong>{{.Code}}</strong>).<br> Falls du dieses Mail nicht angefordert hast, kannst du es einfach ignorieren.
+ ButtonText: Initialisierung abschliessen
+ - MessageTextType: PasswordReset
+ Language: de
+ Title: Zitadel - Passwort zurücksetzen
+ PreHeader: Passwort zurücksetzen
+ Subject: Passwort zurücksetzen
+ Greeting: Hallo {{.FirstName}} {{.LastName}},
+ Text: Wir haben eine Anfrage für das Zurücksetzen deines Passwortes bekommen. Du kannst den untenstehenden Button verwenden, um dein Passwort zurückzusetzen <br>(Code <strong>{{.Code}}</strong>).<br> Falls du dieses Mail nicht angefordert hast, kannst du es ignorieren.
+ ButtonText: Passwort zurücksetzen
+ - MessageTextType: VerifyEmail
+ Language: de
+ Title: Zitadel - Email verifizieren
+ PreHeader: Email verifizieren
+ Subject: Email verifizieren
+ Greeting: Hallo {{.FirstName}} {{.LastName}},
+ Text: Eine neue E-Mail Adresse wurde hinzugefügt. Bitte verwende den untenstehenden Button um diese zu verifizieren <br>(Code <strong>{{.Code}}</strong>).<br> Falls du deine E-Mail Adresse nicht selber hinzugefügt hast, kannst du dieses E-Mail ignorieren.
+ ButtonText: Email verifizieren
+ - MessageTextType: VerifyPhone
+ Language: de
+ Title: Zitadel - Telefonnummer verifizieren
+ PreHeader: Telefonnummer verifizieren
+ Subject: Telefonnummer verifizieren
+ Greeting: Hallo {{.FirstName}} {{.LastName}},
+ Text: Eine Telefonnummer wurde hinzugefügt. Bitte verifiziere diese in dem du folgenden Code eingibst (Code {{.Code}})
+ ButtonText: Telefon verifizieren
+ - MessageTextType: DomainClaimed
+ Language: de
+ Title: Zitadel - Domain wurde beansprucht
+ PreHeader: Email / Username ändern
+ Subject: Domain wurde beansprucht
+ Greeting: Hallo {{.FirstName}} {{.LastName}},
+ Text: Die Domain {{.Domain}} wurde von einer Organisation beansprucht. Dein derzeitiger User {{.Username}} ist nicht Teil dieser Organisation. Daher musst du beim nächsten Login eine neue Email hinterlegen. Für diesen Login haben wir dir einen temporären Usernamen ({{.TempUsername}}) erstellt.
+ ButtonText: Login
+ - MessageTextType: InitCode
+ Language: en
+ Title: Zitadel - Initialize User
+ PreHeader: Initialize User
+ Subject: Initialize User
+ Greeting: Hello {{.FirstName}} {{.LastName}},
+ Text: This user was created in Zitadel. Use the username {{.PreferredLoginName}} to login. Please click the button below to finish the initialization process. (Code {{.Code}}) If you didn't ask for this mail, please ignore it.
+ ButtonText: Finish initialization
+ - MessageTextType: PasswordReset
+ Language: en
+ Title: Zitadel - Reset password
+ PreHeader: Reset password
+ Subject: Reset password
+ Greeting: Hello {{.FirstName}} {{.LastName}},
+ Text: We received a password reset request. Please use the button below to reset your password. (Code {{.Code}}) If you didn't ask for this mail, please ignore it.
+ ButtonText: Reset password
+ - MessageTextType: VerifyEmail
+ Language: en
+ Title: Zitadel - Verify email
+ PreHeader: Verify email
+ Subject: Verify email
+ Greeting: Hello {{.FirstName}} {{.LastName}},
+ Text: A new email has been added. Please use the button below to verify your mail. (Code {{.Code}}) If you din't add a new email, please ignore this email.
+ ButtonText: Verify email
+ - MessageTextType: VerifyPhone
+ Language: en
+ Title: Zitadel - Verify phone
+ PreHeader: Verify phone
+ Subject: Verify phone
+ Greeting: Hello {{.FirstName}} {{.LastName}},
+ Text: A new phonenumber has been added. Please use the following code to verify it {{.Code}}.
+ ButtonText: Verify phone
+ - MessageTextType: DomainClaimed
+ Language: en
+ Title: Zitadel - Domain has been claimed
+ PreHeader: Change email / username
+ Subject: Domain has been claimed
+ Greeting: Hello {{.FirstName}} {{.LastName}},
+ Text: The domain {{.Domain}} has been claimed by an organisation. Your current user {{.UserName}} is not part of this organisation. Therefore you'll have to change your email when you login. We have created a temporary username ({{.TempUsername}}) for this login.
+ ButtonText: Login
\ No newline at end of file
diff --git a/console/src/app/modules/features/features.component.html b/console/src/app/modules/features/features.component.html
index 20d24297a3..3fbca69eef 100644
--- a/console/src/app/modules/features/features.component.html
+++ b/console/src/app/modules/features/features.component.html
@@ -141,7 +141,15 @@
-
+
+
+
+ {{'FEATURES.DATA.CUSTOMTEXT' | translate}}
+
+
+
+
diff --git a/console/src/app/modules/features/features.component.ts b/console/src/app/modules/features/features.component.ts
index 7f6d03c2e2..7444f99a61 100644
--- a/console/src/app/modules/features/features.component.ts
+++ b/console/src/app/modules/features/features.component.ts
@@ -161,6 +161,7 @@ export class FeaturesComponent implements OnDestroy {
req.setLabelPolicyPrivateLabel(this.features.labelPolicyPrivateLabel);
req.setLabelPolicyWatermark(this.features.labelPolicyWatermark);
req.setCustomDomain(this.features.customDomain);
+ req.setCustomText(this.features.customText);
this.adminService.setOrgFeatures(req).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
@@ -181,6 +182,7 @@ export class FeaturesComponent implements OnDestroy {
dreq.setLabelPolicyPrivateLabel(this.features.labelPolicyPrivateLabel);
dreq.setLabelPolicyWatermark(this.features.labelPolicyWatermark);
dreq.setCustomDomain(this.features.customDomain);
+ dreq.setCustomText(this.features.customText);
this.adminService.setDefaultFeatures(dreq).then(() => {
this.toast.showInfo('POLICY.TOAST.SET', true);
diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json
index 39e1e5ba7f..9ac6d424e7 100644
--- a/console/src/assets/i18n/de.json
+++ b/console/src/assets/i18n/de.json
@@ -611,12 +611,13 @@
"LOGINPOLICYPASSWORDRESET": "Login Richtlinie: Passwort vergessen Link nicht anzeigen - benutzerdefiniert",
"LOGINPOLICYREGISTRATION": "Login Richtlinie: Registration erlauben - benutzerdefiniert",
"LOGINPOLICYIDP": "Login Richtlinie: Identity Providers - benutzerdefiniert",
- "LOGINPOLICYFACTORS": "Login Richtlinie: Mltifaktoren - benutzerdefiniert",
+ "LOGINPOLICYFACTORS": "Login Richtlinie: Multifaktoren - benutzerdefiniert",
"LOGINPOLICYPASSWORDLESS": "Login Richtlinie: Passwortlose Authentifizierung - benutzerdefiniert",
"LOGINPOLICYCOMPLEXITYPOLICY": "Passwortkomplexitäts Richtlinie - benutzerdefiniert",
"LABELPOLICYPRIVATELABEL": "Label Richtlinie - benutzerdefiniert",
"LABELPOLICYWATERMARK": "Label Richtlinie - Wasserzeichen",
- "CUSTOMDOMAIN": "Domänen Verifikation - verfügbar"
+ "CUSTOMDOMAIN": "Domänen Verifikation - verfügbar",
+ "CUSTOMTEXT": "Benutzerdefinierte Texte"
},
"TIERSTATES": {
"0": "Aktiv",
diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json
index d9b9d838c4..19b034e29f 100644
--- a/console/src/assets/i18n/en.json
+++ b/console/src/assets/i18n/en.json
@@ -616,7 +616,8 @@
"LOGINPOLICYCOMPLEXITYPOLICY": "Password Complexity Policy - custom",
"LABELPOLICYPRIVATELABEL": "Label Richtlinie - benutzerdefiniert",
"LABELPOLICYWATERMARK": "Label Richtlinie - Wasserzeichen",
- "CUSTOMDOMAIN": "Domain Verification - available"
+ "CUSTOMDOMAIN": "Domain Verification - available",
+ "CUSTOMTEXT": "Custom texts"
},
"TIERSTATES": {
"0": "Active",
diff --git a/docs/docs/apis/proto/admin.md b/docs/docs/apis/proto/admin.md
index a9d252a8a6..a2a8cc8ef2 100644
--- a/docs/docs/apis/proto/admin.md
+++ b/docs/docs/apis/proto/admin.md
@@ -528,6 +528,121 @@ it impacts all organisations without a customised policy
+### GetDefaultInitMessageText
+
+> **rpc** GetDefaultInitMessageText([GetDefaultInitMessageTextRequest](#getdefaultinitmessagetextrequest))
+[GetDefaultInitMessageTextResponse](#getdefaultinitmessagetextresponse)
+
+Returns the custom text for initial message
+
+
+
+
+### SetDefaultInitMessageText
+
+> **rpc** SetDefaultInitMessageText([SetDefaultInitMessageTextRequest](#setdefaultinitmessagetextrequest))
+[SetDefaultInitMessageTextResponse](#setdefaultinitmessagetextresponse)
+
+Sets the default custom text for initial message
+it impacts all organisations without customized initial message text
+The Following Variables can be used:
+{{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
+
+
+
+
+### GetDefaultPasswordResetMessageText
+
+> **rpc** GetDefaultPasswordResetMessageText([GetDefaultPasswordResetMessageTextRequest](#getdefaultpasswordresetmessagetextrequest))
+[GetDefaultPasswordResetMessageTextResponse](#getdefaultpasswordresetmessagetextresponse)
+
+Returns the custom text for password reset message
+
+
+
+
+### SetDefaultPasswordResetMessageText
+
+> **rpc** SetDefaultPasswordResetMessageText([SetDefaultPasswordResetMessageTextRequest](#setdefaultpasswordresetmessagetextrequest))
+[SetDefaultPasswordResetMessageTextResponse](#setdefaultpasswordresetmessagetextresponse)
+
+Sets the default custom text for password reset message
+it impacts all organisations without customized password reset message text
+The Following Variables can be used:
+{{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
+
+
+
+
+### GetDefaultVerifyEmailMessageText
+
+> **rpc** GetDefaultVerifyEmailMessageText([GetDefaultVerifyEmailMessageTextRequest](#getdefaultverifyemailmessagetextrequest))
+[GetDefaultVerifyEmailMessageTextResponse](#getdefaultverifyemailmessagetextresponse)
+
+Returns the custom text for verify email message
+
+
+
+
+### SetDefaultVerifyEmailMessageText
+
+> **rpc** SetDefaultVerifyEmailMessageText([SetDefaultVerifyEmailMessageTextRequest](#setdefaultverifyemailmessagetextrequest))
+[SetDefaultVerifyEmailMessageTextResponse](#setdefaultverifyemailmessagetextresponse)
+
+Sets the default custom text for verify email message
+it impacts all organisations without customized verify email message text
+The Following Variables can be used:
+{{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
+
+
+
+
+### GetDefaultVerifyPhoneMessageText
+
+> **rpc** GetDefaultVerifyPhoneMessageText([GetDefaultVerifyPhoneMessageTextRequest](#getdefaultverifyphonemessagetextrequest))
+[GetDefaultVerifyPhoneMessageTextResponse](#getdefaultverifyphonemessagetextresponse)
+
+Returns the custom text for verify phone message
+
+
+
+
+### SetDefaultVerifyPhoneMessageText
+
+> **rpc** SetDefaultVerifyPhoneMessageText([SetDefaultVerifyPhoneMessageTextRequest](#setdefaultverifyphonemessagetextrequest))
+[SetDefaultVerifyPhoneMessageTextResponse](#setdefaultverifyphonemessagetextresponse)
+
+Sets the default custom text for verify phone message
+it impacts all organisations without customized verify phone message text
+The Following Variables can be used:
+{{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
+
+
+
+
+### GetDefaultDomainClaimedMessageText
+
+> **rpc** GetDefaultDomainClaimedMessageText([GetDefaultDomainClaimedMessageTextRequest](#getdefaultdomainclaimedmessagetextrequest))
+[GetDefaultDomainClaimedMessageTextResponse](#getdefaultdomainclaimedmessagetextresponse)
+
+Returns the custom text for domain claimed message
+
+
+
+
+### SetDefaultDomainClaimedMessageText
+
+> **rpc** SetDefaultDomainClaimedMessageText([SetDefaultDomainClaimedMessageTextRequest](#setdefaultdomainclaimedmessagetextrequest))
+[SetDefaultDomainClaimedMessageTextResponse](#setdefaultdomainclaimedmessagetextresponse)
+
+Sets the default custom text for domain claimed phone message
+it impacts all organisations without customized verify phone message text
+The Following Variables can be used:
+{{.Domain}} {{.TempUsername}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
+
+
+
+
### ListIAMMemberRoles
> **rpc** ListIAMMemberRoles([ListIAMMemberRolesRequest](#listiammemberrolesrequest))
@@ -877,6 +992,28 @@ This is an empty response
+### GetDefaultDomainClaimedMessageTextRequest
+This is an empty request
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+
+
+
+
+### GetDefaultDomainClaimedMessageTextResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| custom_text | zitadel.text.v1.MessageCustomText | - | |
+
+
+
+
### GetDefaultFeaturesRequest
@@ -894,6 +1031,94 @@ This is an empty response
+### GetDefaultInitMessageTextRequest
+This is an empty request
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+
+
+
+
+### GetDefaultInitMessageTextResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| custom_text | zitadel.text.v1.MessageCustomText | - | |
+
+
+
+
+### GetDefaultPasswordResetMessageTextRequest
+This is an empty request
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+
+
+
+
+### GetDefaultPasswordResetMessageTextResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| custom_text | zitadel.text.v1.MessageCustomText | - | |
+
+
+
+
+### GetDefaultVerifyEmailMessageTextRequest
+This is an empty request
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+
+
+
+
+### GetDefaultVerifyEmailMessageTextResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| custom_text | zitadel.text.v1.MessageCustomText | - | |
+
+
+
+
+### GetDefaultVerifyPhoneMessageTextRequest
+This is an empty request
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+
+
+
+
+### GetDefaultVerifyPhoneMessageTextResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| custom_text | zitadel.text.v1.MessageCustomText | - | |
+
+
+
+
### GetIDPByIDRequest
@@ -1593,6 +1818,35 @@ This is an empty request
+### SetDefaultDomainClaimedMessageTextRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+| title | string | - | string.max_len: 200
|
+| pre_header | string | - | string.max_len: 200
|
+| subject | string | - | string.max_len: 200
|
+| greeting | string | - | string.max_len: 200
|
+| text | string | - | string.max_len: 800
|
+| button_text | string | - | string.max_len: 200
|
+| footer_text | string | - | string.max_len: 200
|
+
+
+
+
+### SetDefaultDomainClaimedMessageTextResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+
+
+
+
### SetDefaultFeaturesRequest
@@ -1613,6 +1867,7 @@ This is an empty request
| login_policy_password_reset | bool | - | |
| label_policy_private_label | bool | - | |
| label_policy_watermark | bool | - | |
+| custom_text | bool | - | |
@@ -1628,6 +1883,122 @@ This is an empty request
+### SetDefaultInitMessageTextRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+| title | string | - | string.max_len: 200
|
+| pre_header | string | - | string.max_len: 200
|
+| subject | string | - | string.max_len: 200
|
+| greeting | string | - | string.max_len: 200
|
+| text | string | - | string.max_len: 1000
|
+| button_text | string | - | string.max_len: 200
|
+| footer_text | string | - | string.max_len: 200
|
+
+
+
+
+### SetDefaultInitMessageTextResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+
+
+
+
+### SetDefaultPasswordResetMessageTextRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+| title | string | - | string.max_len: 200
|
+| pre_header | string | - | string.max_len: 200
|
+| subject | string | - | string.max_len: 200
|
+| greeting | string | - | string.max_len: 200
|
+| text | string | - | string.max_len: 800
|
+| button_text | string | - | string.max_len: 200
|
+| footer_text | string | - | string.max_len: 200
|
+
+
+
+
+### SetDefaultPasswordResetMessageTextResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+
+
+
+
+### SetDefaultVerifyEmailMessageTextRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+| title | string | - | string.max_len: 200
|
+| pre_header | string | - | string.max_len: 200
|
+| subject | string | - | string.max_len: 200
|
+| greeting | string | - | string.max_len: 200
|
+| text | string | - | string.max_len: 800
|
+| button_text | string | - | string.max_len: 200
|
+| footer_text | string | - | string.max_len: 200
|
+
+
+
+
+### SetDefaultVerifyEmailMessageTextResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+
+
+
+
+### SetDefaultVerifyPhoneMessageTextRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+| title | string | - | string.max_len: 200
|
+| pre_header | string | - | string.max_len: 200
|
+| subject | string | - | string.max_len: 200
|
+| greeting | string | - | string.max_len: 200
|
+| text | string | - | string.max_len: 800
|
+| button_text | string | - | string.max_len: 200
|
+| footer_text | string | - | string.max_len: 200
|
+
+
+
+
+### SetDefaultVerifyPhoneMessageTextResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+
+
+
+
### SetOrgFeaturesRequest
@@ -1651,6 +2022,7 @@ This is an empty request
| login_policy_password_reset | bool | - | |
| label_policy_private_label | bool | - | |
| label_policy_watermark | bool | - | |
+| custom_text | bool | - | |
diff --git a/docs/docs/apis/proto/management.md b/docs/docs/apis/proto/management.md
index 492e4bd0b6..66e7ab5248 100644
--- a/docs/docs/apis/proto/management.md
+++ b/docs/docs/apis/proto/management.md
@@ -1712,6 +1712,176 @@ The default policy of the IAM will trigger after
+### GetCustomInitMessageText
+
+> **rpc** GetCustomInitMessageText([GetCustomInitMessageTextRequest](#getcustominitmessagetextrequest))
+[GetCustomInitMessageTextResponse](#getcustominitmessagetextresponse)
+
+Returns the custom text for initial message
+
+
+
+
+### SetCustomInitMessageText
+
+> **rpc** SetCustomInitMessageText([SetCustomInitMessageTextRequest](#setcustominitmessagetextrequest))
+[SetCustomInitMessageTextResponse](#setcustominitmessagetextresponse)
+
+Sets the default custom text for initial message
+it impacts all organisations without customized initial message text
+The Following Variables can be used:
+{{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
+
+
+
+
+### ResetCustomInitMessageTextToDefault
+
+> **rpc** ResetCustomInitMessageTextToDefault([ResetCustomInitMessageTextToDefaultRequest](#resetcustominitmessagetexttodefaultrequest))
+[ResetCustomInitMessageTextToDefaultResponse](#resetcustominitmessagetexttodefaultresponse)
+
+Removes the custom init message text of the organisation
+The default text of the IAM will trigger after
+
+
+
+
+### GetCustomPasswordResetMessageText
+
+> **rpc** GetCustomPasswordResetMessageText([GetCustomPasswordResetMessageTextRequest](#getcustompasswordresetmessagetextrequest))
+[GetCustomPasswordResetMessageTextResponse](#getcustompasswordresetmessagetextresponse)
+
+Returns the custom text for password reset message
+
+
+
+
+### SetCustomPasswordResetMessageText
+
+> **rpc** SetCustomPasswordResetMessageText([SetCustomPasswordResetMessageTextRequest](#setcustompasswordresetmessagetextrequest))
+[SetCustomPasswordResetMessageTextResponse](#setcustompasswordresetmessagetextresponse)
+
+Sets the default custom text for password reset message
+it impacts all organisations without customized password reset message text
+The Following Variables can be used:
+{{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
+
+
+
+
+### ResetCustomPasswordResetMessageTextToDefault
+
+> **rpc** ResetCustomPasswordResetMessageTextToDefault([ResetCustomPasswordResetMessageTextToDefaultRequest](#resetcustompasswordresetmessagetexttodefaultrequest))
+[ResetCustomPasswordResetMessageTextToDefaultResponse](#resetcustompasswordresetmessagetexttodefaultresponse)
+
+Removes the custom init message text of the organisation
+The default text of the IAM will trigger after
+
+
+
+
+### GetCustomVerifyEmailMessageText
+
+> **rpc** GetCustomVerifyEmailMessageText([GetCustomVerifyEmailMessageTextRequest](#getcustomverifyemailmessagetextrequest))
+[GetCustomVerifyEmailMessageTextResponse](#getcustomverifyemailmessagetextresponse)
+
+Returns the custom text for verify email message
+
+
+
+
+### SetCustomVerifyEmailMessageText
+
+> **rpc** SetCustomVerifyEmailMessageText([SetCustomVerifyEmailMessageTextRequest](#setcustomverifyemailmessagetextrequest))
+[SetCustomVerifyEmailMessageTextResponse](#setcustomverifyemailmessagetextresponse)
+
+Sets the default custom text for verify email message
+it impacts all organisations without customized verify email message text
+The Following Variables can be used:
+{{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
+
+
+
+
+### ResetCustomVerifyEmailMessageTextToDefault
+
+> **rpc** ResetCustomVerifyEmailMessageTextToDefault([ResetCustomVerifyEmailMessageTextToDefaultRequest](#resetcustomverifyemailmessagetexttodefaultrequest))
+[ResetCustomVerifyEmailMessageTextToDefaultResponse](#resetcustomverifyemailmessagetexttodefaultresponse)
+
+Removes the custom init message text of the organisation
+The default text of the IAM will trigger after
+
+
+
+
+### GetCustomVerifyPhoneMessageText
+
+> **rpc** GetCustomVerifyPhoneMessageText([GetCustomVerifyPhoneMessageTextRequest](#getcustomverifyphonemessagetextrequest))
+[GetCustomVerifyPhoneMessageTextResponse](#getcustomverifyphonemessagetextresponse)
+
+Returns the custom text for verify email message
+
+
+
+
+### SetCustomVerifyPhoneMessageText
+
+> **rpc** SetCustomVerifyPhoneMessageText([SetCustomVerifyPhoneMessageTextRequest](#setcustomverifyphonemessagetextrequest))
+[SetCustomVerifyPhoneMessageTextResponse](#setcustomverifyphonemessagetextresponse)
+
+Sets the default custom text for verify email message
+it impacts all organisations without customized verify email message text
+The Following Variables can be used:
+{{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
+
+
+
+
+### ResetCustomVerifyPhoneMessageTextToDefault
+
+> **rpc** ResetCustomVerifyPhoneMessageTextToDefault([ResetCustomVerifyPhoneMessageTextToDefaultRequest](#resetcustomverifyphonemessagetexttodefaultrequest))
+[ResetCustomVerifyPhoneMessageTextToDefaultResponse](#resetcustomverifyphonemessagetexttodefaultresponse)
+
+Removes the custom init message text of the organisation
+The default text of the IAM will trigger after
+
+
+
+
+### GetCustomDomainClaimedMessageText
+
+> **rpc** GetCustomDomainClaimedMessageText([GetCustomDomainClaimedMessageTextRequest](#getcustomdomainclaimedmessagetextrequest))
+[GetCustomDomainClaimedMessageTextResponse](#getcustomdomainclaimedmessagetextresponse)
+
+Returns the custom text for domain claimed message
+
+
+
+
+### SetCustomDomainClaimedMessageCustomText
+
+> **rpc** SetCustomDomainClaimedMessageCustomText([SetCustomDomainClaimedMessageTextRequest](#setcustomdomainclaimedmessagetextrequest))
+[SetCustomDomainClaimedMessageTextResponse](#setcustomdomainclaimedmessagetextresponse)
+
+Sets the default custom text for domain claimed message
+it impacts all organisations without customized domain claimed message text
+The Following Variables can be used:
+{{.Domain}} {{.TempUsername}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
+
+
+
+
+### ResetCustomDomainClaimedMessageTextToDefault
+
+> **rpc** ResetCustomDomainClaimedMessageTextToDefault([ResetCustomDomainClaimedMessageTextToDefaultRequest](#resetcustomdomainclaimedmessagetexttodefaultrequest))
+[ResetCustomDomainClaimedMessageTextToDefaultResponse](#resetcustomdomainclaimedmessagetexttodefaultresponse)
+
+Removes the custom init message text of the organisation
+The default text of the IAM will trigger after
+
+
+
+
### GetOrgIDPByID
> **rpc** GetOrgIDPByID([GetOrgIDPByIDRequest](#getorgidpbyidrequest))
@@ -2758,6 +2928,116 @@ This is an empty request
+### GetCustomDomainClaimedMessageTextRequest
+This is an empty request
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+
+
+
+
+### GetCustomDomainClaimedMessageTextResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| custom_text | zitadel.text.v1.MessageCustomText | - | |
+
+
+
+
+### GetCustomInitMessageTextRequest
+This is an empty request
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+
+
+
+
+### GetCustomInitMessageTextResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| custom_text | zitadel.text.v1.MessageCustomText | - | |
+
+
+
+
+### GetCustomPasswordResetMessageTextRequest
+This is an empty request
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+
+
+
+
+### GetCustomPasswordResetMessageTextResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| custom_text | zitadel.text.v1.MessageCustomText | - | |
+
+
+
+
+### GetCustomVerifyEmailMessageTextRequest
+This is an empty request
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+
+
+
+
+### GetCustomVerifyEmailMessageTextResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| custom_text | zitadel.text.v1.MessageCustomText | - | |
+
+
+
+
+### GetCustomVerifyPhoneMessageTextRequest
+This is an empty request
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+
+
+
+
+### GetCustomVerifyPhoneMessageTextResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| custom_text | zitadel.text.v1.MessageCustomText | - | |
+
+
+
+
### GetDefaultLabelPolicyRequest
This is an empty request
@@ -4972,6 +5252,116 @@ This is an empty response
+### ResetCustomDomainClaimedMessageTextToDefaultRequest
+This is an empty request
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+
+
+
+
+### ResetCustomDomainClaimedMessageTextToDefaultResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+
+
+
+
+### ResetCustomInitMessageTextToDefaultRequest
+This is an empty request
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+
+
+
+
+### ResetCustomInitMessageTextToDefaultResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+
+
+
+
+### ResetCustomPasswordResetMessageTextToDefaultRequest
+This is an empty request
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+
+
+
+
+### ResetCustomPasswordResetMessageTextToDefaultResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+
+
+
+
+### ResetCustomVerifyEmailMessageTextToDefaultRequest
+This is an empty request
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+
+
+
+
+### ResetCustomVerifyEmailMessageTextToDefaultResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+
+
+
+
+### ResetCustomVerifyPhoneMessageTextToDefaultRequest
+This is an empty request
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+
+
+
+
+### ResetCustomVerifyPhoneMessageTextToDefaultResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+
+
+
+
### ResetLabelPolicyToDefaultRequest
This is an empty request
@@ -5080,6 +5470,151 @@ This is an empty request
+### SetCustomDomainClaimedMessageTextRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+| title | string | - | string.max_len: 200
|
+| pre_header | string | - | string.max_len: 200
|
+| subject | string | - | string.max_len: 200
|
+| greeting | string | - | string.max_len: 200
|
+| text | string | - | string.max_len: 800
|
+| button_text | string | - | string.max_len: 200
|
+| footer_text | string | - | string.max_len: 200
|
+
+
+
+
+### SetCustomDomainClaimedMessageTextResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+
+
+
+
+### SetCustomInitMessageTextRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+| title | string | - | string.max_len: 200
|
+| pre_header | string | - | string.max_len: 200
|
+| subject | string | - | string.max_len: 200
|
+| greeting | string | - | string.max_len: 200
|
+| text | string | - | string.max_len: 800
|
+| button_text | string | - | string.max_len: 200
|
+| footer_text | string | - | string.max_len: 200
|
+
+
+
+
+### SetCustomInitMessageTextResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+
+
+
+
+### SetCustomPasswordResetMessageTextRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+| title | string | - | string.max_len: 200
|
+| pre_header | string | - | string.max_len: 200
|
+| subject | string | - | string.max_len: 200
|
+| greeting | string | - | string.max_len: 200
|
+| text | string | - | string.max_len: 800
|
+| button_text | string | - | string.max_len: 200
|
+| footer_text | string | - | string.max_len: 200
|
+
+
+
+
+### SetCustomPasswordResetMessageTextResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+
+
+
+
+### SetCustomVerifyEmailMessageTextRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+| title | string | - | string.max_len: 200
|
+| pre_header | string | - | string.max_len: 200
|
+| subject | string | - | string.max_len: 200
|
+| greeting | string | - | string.max_len: 200
|
+| text | string | - | string.max_len: 800
|
+| button_text | string | - | string.max_len: 200
|
+| footer_text | string | - | string.max_len: 200
|
+
+
+
+
+### SetCustomVerifyEmailMessageTextResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+
+
+
+
+### SetCustomVerifyPhoneMessageTextRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| language | string | - | string.min_len: 1
string.max_len: 200
|
+| title | string | - | string.max_len: 200
|
+| pre_header | string | - | string.max_len: 200
|
+| subject | string | - | string.max_len: 200
|
+| greeting | string | - | string.max_len: 200
|
+| text | string | - | string.max_len: 800
|
+| button_text | string | - | string.max_len: 200
|
+| footer_text | string | - | string.max_len: 200
|
+
+
+
+
+### SetCustomVerifyPhoneMessageTextResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+
+
+
+
### SetHumanInitialPasswordRequest
diff --git a/internal/admin/repository/eventsourcing/eventstore/iam.go b/internal/admin/repository/eventsourcing/eventstore/iam.go
index be2902f790..670809d271 100644
--- a/internal/admin/repository/eventsourcing/eventstore/iam.go
+++ b/internal/admin/repository/eventsourcing/eventstore/iam.go
@@ -2,16 +2,18 @@ package eventstore
import (
"context"
+ "strings"
+
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/eventstore/v1/models"
iam_view "github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/user/repository/view/model"
- "strings"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/logging"
+
admin_view "github.com/caos/zitadel/internal/admin/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/config/systemdefaults"
iam_model "github.com/caos/zitadel/internal/iam/model"
@@ -347,21 +349,21 @@ func (repo *IAMRepository) SearchIAMMembersx(ctx context.Context, request *iam_m
return result, nil
}
-func (repo *IAMRepository) GetDefaultMailTexts(ctx context.Context) (*iam_model.MailTextsView, error) {
- text, err := repo.View.MailTexts(repo.SystemDefaults.IamID)
+func (repo *IAMRepository) GetDefaultMessageTexts(ctx context.Context) (*iam_model.MessageTextsView, error) {
+ text, err := repo.View.MessageTexts(repo.SystemDefaults.IamID)
if err != nil {
return nil, err
}
- return iam_es_model.MailTextsViewToModel(text, true), err
+ return iam_es_model.MessageTextsViewToModel(text, true), err
}
-func (repo *IAMRepository) GetDefaultMailText(ctx context.Context, textType string, language string) (*iam_model.MailTextView, error) {
- text, err := repo.View.MailTextByIDs(repo.SystemDefaults.IamID, textType, language)
+func (repo *IAMRepository) GetDefaultMessageText(ctx context.Context, textType, lang string) (*iam_model.MessageTextView, error) {
+ text, err := repo.View.MessageTextByIDs(repo.SystemDefaults.IamID, textType, lang)
if err != nil {
return nil, err
}
text.Default = true
- return iam_es_model.MailTextViewToModel(text), err
+ return iam_es_model.MessageTextViewToModel(text), err
}
func (repo *IAMRepository) getIAMEvents(ctx context.Context, sequence uint64) ([]*models.Event, error) {
diff --git a/internal/admin/repository/eventsourcing/handler/handler.go b/internal/admin/repository/eventsourcing/handler/handler.go
index b8db6cd996..9695aeb756 100644
--- a/internal/admin/repository/eventsourcing/handler/handler.go
+++ b/internal/admin/repository/eventsourcing/handler/handler.go
@@ -62,8 +62,8 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
defaults),
newMailTemplate(
handler{view, bulkLimit, configs.cycleDuration("MailTemplate"), errorCount, es}),
- newMailText(
- handler{view, bulkLimit, configs.cycleDuration("MailText"), errorCount, es}),
+ newMessageText(
+ handler{view, bulkLimit, configs.cycleDuration("MessageText"), errorCount, es}),
newFeatures(
handler{view, bulkLimit, configs.cycleDuration("Features"), errorCount, es}),
}
diff --git a/internal/admin/repository/eventsourcing/handler/mail_text.go b/internal/admin/repository/eventsourcing/handler/mail_text.go
deleted file mode 100644
index 7656fbc1b9..0000000000
--- a/internal/admin/repository/eventsourcing/handler/mail_text.go
+++ /dev/null
@@ -1,108 +0,0 @@
-package handler
-
-import (
- "github.com/caos/logging"
- "github.com/caos/zitadel/internal/eventstore/v1"
-
- es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
- "github.com/caos/zitadel/internal/eventstore/v1/query"
- "github.com/caos/zitadel/internal/eventstore/v1/spooler"
- "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
- iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
- iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
-)
-
-type MailText struct {
- handler
- subscription *v1.Subscription
-}
-
-func newMailText(handler handler) *MailText {
- h := &MailText{
- handler: handler,
- }
-
- h.subscribe()
-
- return h
-}
-
-func (m *MailText) subscribe() {
- m.subscription = m.es.Subscribe(m.AggregateTypes()...)
- go func() {
- for event := range m.subscription.Events {
- query.ReduceEvent(m, event)
- }
- }()
-}
-
-const (
- mailTextTable = "adminapi.mail_texts"
-)
-
-func (m *MailText) ViewModel() string {
- return mailTextTable
-}
-
-func (_ *MailText) AggregateTypes() []es_models.AggregateType {
- return []es_models.AggregateType{iam_es_model.IAMAggregate}
-}
-
-func (p *MailText) CurrentSequence() (uint64, error) {
- sequence, err := p.view.GetLatestMailTextSequence()
- if err != nil {
- return 0, err
- }
- return sequence.CurrentSequence, nil
-}
-
-func (m *MailText) EventQuery() (*es_models.SearchQuery, error) {
- sequence, err := m.view.GetLatestMailTextSequence()
- if err != nil {
- return nil, err
- }
- return es_models.NewSearchQuery().
- AggregateTypeFilter(m.AggregateTypes()...).
- LatestSequenceFilter(sequence.CurrentSequence), nil
-}
-
-func (m *MailText) Reduce(event *es_models.Event) (err error) {
- switch event.AggregateType {
- case model.IAMAggregate:
- err = m.processMailText(event)
- }
- return err
-}
-
-func (m *MailText) processMailText(event *es_models.Event) (err error) {
- mailText := new(iam_model.MailTextView)
- switch event.Type {
- case model.MailTextAdded:
- err = mailText.AppendEvent(event)
- case model.MailTextChanged:
- err = mailText.SetData(event)
- if err != nil {
- return err
- }
- mailText, err = m.view.MailTextByIDs(event.AggregateID, mailText.MailTextType, mailText.Language)
- if err != nil {
- return err
- }
- err = mailText.AppendEvent(event)
- default:
- return m.view.ProcessedMailTextSequence(event)
- }
- if err != nil {
- return err
- }
- return m.view.PutMailText(mailText, event)
-}
-
-func (m *MailText) OnError(event *es_models.Event, err error) error {
- logging.LogWithFields("HANDL-5jk84", "id", event.AggregateID).WithError(err).Warn("something went wrong in label mailText handler")
- return spooler.HandleError(event, err, m.view.GetLatestMailTextFailedEvent, m.view.ProcessedMailTextFailedEvent, m.view.ProcessedMailTextSequence, m.errorCountUntilSkip)
-}
-
-func (o *MailText) OnSuccess() error {
- return spooler.HandleSuccess(o.view.UpdateMailTextSpoolerRunTimestamp)
-}
diff --git a/internal/admin/repository/eventsourcing/handler/message_text.go b/internal/admin/repository/eventsourcing/handler/message_text.go
new file mode 100644
index 0000000000..6a6fc5ca2c
--- /dev/null
+++ b/internal/admin/repository/eventsourcing/handler/message_text.go
@@ -0,0 +1,116 @@
+package handler
+
+import (
+ "github.com/caos/logging"
+ caos_errs "github.com/caos/zitadel/internal/errors"
+ "github.com/caos/zitadel/internal/eventstore/v1"
+
+ es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
+ "github.com/caos/zitadel/internal/eventstore/v1/query"
+ "github.com/caos/zitadel/internal/eventstore/v1/spooler"
+ "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
+ iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
+ iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
+)
+
+type MessageText struct {
+ handler
+ subscription *v1.Subscription
+}
+
+func newMessageText(handler handler) *MessageText {
+ h := &MessageText{
+ handler: handler,
+ }
+
+ h.subscribe()
+
+ return h
+}
+
+func (m *MessageText) subscribe() {
+ m.subscription = m.es.Subscribe(m.AggregateTypes()...)
+ go func() {
+ for event := range m.subscription.Events {
+ query.ReduceEvent(m, event)
+ }
+ }()
+}
+
+const (
+ mailTextTable = "adminapi.message_texts"
+)
+
+func (m *MessageText) ViewModel() string {
+ return mailTextTable
+}
+
+func (_ *MessageText) AggregateTypes() []es_models.AggregateType {
+ return []es_models.AggregateType{iam_es_model.IAMAggregate}
+}
+
+func (p *MessageText) CurrentSequence() (uint64, error) {
+ sequence, err := p.view.GetLatestMessageTextSequence()
+ if err != nil {
+ return 0, err
+ }
+ return sequence.CurrentSequence, nil
+}
+
+func (m *MessageText) EventQuery() (*es_models.SearchQuery, error) {
+ sequence, err := m.view.GetLatestMessageTextSequence()
+ if err != nil {
+ return nil, err
+ }
+ return es_models.NewSearchQuery().
+ AggregateTypeFilter(m.AggregateTypes()...).
+ LatestSequenceFilter(sequence.CurrentSequence), nil
+}
+
+func (m *MessageText) Reduce(event *es_models.Event) (err error) {
+ switch event.AggregateType {
+ case model.IAMAggregate:
+ err = m.processMessageText(event)
+ }
+ return err
+}
+
+func (m *MessageText) processMessageText(event *es_models.Event) (err error) {
+ message := new(iam_model.MessageTextView)
+ switch event.Type {
+ case model.CustomTextSet,
+ model.CustomTextRemoved:
+ text := new(iam_model.CustomText)
+ err = text.SetData(event)
+ if err != nil {
+ return err
+ }
+ message, err = m.view.MessageTextByIDs(event.AggregateID, text.Template, text.Language.String())
+ if err != nil && !caos_errs.IsNotFound(err) {
+ return err
+ }
+ if caos_errs.IsNotFound(err) {
+ err = nil
+ message = new(iam_model.MessageTextView)
+ message.Language = text.Language.String()
+ message.MessageTextType = text.Template
+ message.CreationDate = event.CreationDate
+ }
+ err = message.AppendEvent(event)
+ default:
+ return m.view.ProcessedMessageTextSequence(event)
+ }
+ if err != nil {
+ return err
+ }
+ return m.view.PutMessageText(message, event)
+}
+
+func (m *MessageText) OnError(event *es_models.Event, err error) error {
+ logging.LogWithFields("HANDL-5jk84", "id", event.AggregateID).WithError(err).Warn("something went wrong in label mailText handler")
+ return spooler.HandleError(event, err, m.view.GetLatestMessageTextFailedEvent, m.view.ProcessedMessageTextFailedEvent, m.view.ProcessedMessageTextSequence, m.errorCountUntilSkip)
+}
+
+func (o *MessageText) OnSuccess() error {
+ return spooler.HandleSuccess(o.view.UpdateMessageTextSpoolerRunTimestamp)
+}
diff --git a/internal/admin/repository/eventsourcing/view/mail_texts.go b/internal/admin/repository/eventsourcing/view/mail_texts.go
deleted file mode 100644
index 21aa83b90d..0000000000
--- a/internal/admin/repository/eventsourcing/view/mail_texts.go
+++ /dev/null
@@ -1,48 +0,0 @@
-package view
-
-import (
- "github.com/caos/zitadel/internal/eventstore/v1/models"
- "github.com/caos/zitadel/internal/iam/repository/view"
- "github.com/caos/zitadel/internal/iam/repository/view/model"
- global_view "github.com/caos/zitadel/internal/view/repository"
-)
-
-const (
- mailTextTable = "adminapi.mail_texts"
-)
-
-func (v *View) MailTexts(aggregateID string) ([]*model.MailTextView, error) {
- return view.GetMailTexts(v.Db, mailTextTable, aggregateID)
-}
-
-func (v *View) MailTextByIDs(aggregateID string, textType string, language string) (*model.MailTextView, error) {
- return view.GetMailTextByIDs(v.Db, mailTextTable, aggregateID, textType, language)
-}
-
-func (v *View) PutMailText(template *model.MailTextView, event *models.Event) error {
- err := view.PutMailText(v.Db, mailTextTable, template)
- if err != nil {
- return err
- }
- return v.ProcessedMailTextSequence(event)
-}
-
-func (v *View) GetLatestMailTextSequence() (*global_view.CurrentSequence, error) {
- return v.latestSequence(mailTextTable)
-}
-
-func (v *View) ProcessedMailTextSequence(event *models.Event) error {
- return v.saveCurrentSequence(mailTextTable, event)
-}
-
-func (v *View) UpdateMailTextSpoolerRunTimestamp() error {
- return v.updateSpoolerRunSequence(mailTextTable)
-}
-
-func (v *View) GetLatestMailTextFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
- return v.latestFailedEvent(mailTextTable, sequence)
-}
-
-func (v *View) ProcessedMailTextFailedEvent(failedEvent *global_view.FailedEvent) error {
- return v.saveFailedEvent(failedEvent)
-}
diff --git a/internal/admin/repository/eventsourcing/view/message_texts.go b/internal/admin/repository/eventsourcing/view/message_texts.go
new file mode 100644
index 0000000000..17128d5e17
--- /dev/null
+++ b/internal/admin/repository/eventsourcing/view/message_texts.go
@@ -0,0 +1,48 @@
+package view
+
+import (
+ "github.com/caos/zitadel/internal/eventstore/v1/models"
+ "github.com/caos/zitadel/internal/iam/repository/view"
+ "github.com/caos/zitadel/internal/iam/repository/view/model"
+ global_view "github.com/caos/zitadel/internal/view/repository"
+)
+
+const (
+ messageTextTable = "adminapi.message_texts"
+)
+
+func (v *View) MessageTexts(aggregateID string) ([]*model.MessageTextView, error) {
+ return view.GetMessageTexts(v.Db, messageTextTable, aggregateID)
+}
+
+func (v *View) MessageTextByIDs(aggregateID, textType, lang string) (*model.MessageTextView, error) {
+ return view.GetMessageTextByIDs(v.Db, messageTextTable, aggregateID, textType, lang)
+}
+
+func (v *View) PutMessageText(template *model.MessageTextView, event *models.Event) error {
+ err := view.PutMessageText(v.Db, messageTextTable, template)
+ if err != nil {
+ return err
+ }
+ return v.ProcessedMessageTextSequence(event)
+}
+
+func (v *View) GetLatestMessageTextSequence() (*global_view.CurrentSequence, error) {
+ return v.latestSequence(messageTextTable)
+}
+
+func (v *View) ProcessedMessageTextSequence(event *models.Event) error {
+ return v.saveCurrentSequence(messageTextTable, event)
+}
+
+func (v *View) UpdateMessageTextSpoolerRunTimestamp() error {
+ return v.updateSpoolerRunSequence(messageTextTable)
+}
+
+func (v *View) GetLatestMessageTextFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
+ return v.latestFailedEvent(messageTextTable, sequence)
+}
+
+func (v *View) ProcessedMessageTextFailedEvent(failedEvent *global_view.FailedEvent) error {
+ return v.saveFailedEvent(failedEvent)
+}
diff --git a/internal/admin/repository/iam.go b/internal/admin/repository/iam.go
index a025d472eb..4bf9c0f653 100644
--- a/internal/admin/repository/iam.go
+++ b/internal/admin/repository/iam.go
@@ -28,8 +28,8 @@ type IAMRepository interface {
GetDefaultMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error)
- GetDefaultMailTexts(ctx context.Context) (*iam_model.MailTextsView, error)
- GetDefaultMailText(ctx context.Context, textType string, language string) (*iam_model.MailTextView, error)
+ GetDefaultMessageTexts(ctx context.Context) (*iam_model.MessageTextsView, error)
+ GetDefaultMessageText(ctx context.Context, textType string, language string) (*iam_model.MessageTextView, error)
GetDefaultPasswordComplexityPolicy(ctx context.Context) (*iam_model.PasswordComplexityPolicyView, error)
diff --git a/internal/api/grpc/admin/custom_text.go b/internal/api/grpc/admin/custom_text.go
new file mode 100644
index 0000000000..7e430bd69a
--- /dev/null
+++ b/internal/api/grpc/admin/custom_text.go
@@ -0,0 +1,130 @@
+package admin
+
+import (
+ "context"
+
+ "github.com/caos/zitadel/internal/api/grpc/object"
+ text_grpc "github.com/caos/zitadel/internal/api/grpc/text"
+ "github.com/caos/zitadel/internal/domain"
+ admin_pb "github.com/caos/zitadel/pkg/grpc/admin"
+)
+
+func (s *Server) GetDefaultInitMessageText(ctx context.Context, req *admin_pb.GetDefaultInitMessageTextRequest) (*admin_pb.GetDefaultInitMessageTextResponse, error) {
+ msg, err := s.iam.GetDefaultMessageText(ctx, domain.InitCodeMessageType, req.Language)
+ if err != nil {
+ return nil, err
+ }
+ return &admin_pb.GetDefaultInitMessageTextResponse{
+ CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
+ }, nil
+}
+
+func (s *Server) SetDefaultInitMessageText(ctx context.Context, req *admin_pb.SetDefaultInitMessageTextRequest) (*admin_pb.SetDefaultInitMessageTextResponse, error) {
+ result, err := s.command.SetDefaultMessageText(ctx, SetInitCustomTextToDomain(req))
+ if err != nil {
+ return nil, err
+ }
+ return &admin_pb.SetDefaultInitMessageTextResponse{
+ Details: object.ChangeToDetailsPb(
+ result.Sequence,
+ result.EventDate,
+ result.ResourceOwner,
+ ),
+ }, nil
+}
+
+func (s *Server) GetDefaultPasswordResetMessageText(ctx context.Context, req *admin_pb.GetDefaultPasswordResetMessageTextRequest) (*admin_pb.GetDefaultPasswordResetMessageTextResponse, error) {
+ msg, err := s.iam.GetDefaultMessageText(ctx, domain.PasswordResetMessageType, req.Language)
+ if err != nil {
+ return nil, err
+ }
+ return &admin_pb.GetDefaultPasswordResetMessageTextResponse{
+ CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
+ }, nil
+}
+
+func (s *Server) SetDefaultPasswordResetMessageText(ctx context.Context, req *admin_pb.SetDefaultPasswordResetMessageTextRequest) (*admin_pb.SetDefaultPasswordResetMessageTextResponse, error) {
+ result, err := s.command.SetDefaultMessageText(ctx, SetPasswordResetCustomTextToDomain(req))
+ if err != nil {
+ return nil, err
+ }
+ return &admin_pb.SetDefaultPasswordResetMessageTextResponse{
+ Details: object.ChangeToDetailsPb(
+ result.Sequence,
+ result.EventDate,
+ result.ResourceOwner,
+ ),
+ }, nil
+}
+
+func (s *Server) GetDefaultVerifyEmailMessageText(ctx context.Context, req *admin_pb.GetDefaultVerifyEmailMessageTextRequest) (*admin_pb.GetDefaultVerifyEmailMessageTextResponse, error) {
+ msg, err := s.iam.GetDefaultMessageText(ctx, domain.VerifyEmailMessageType, req.Language)
+ if err != nil {
+ return nil, err
+ }
+ return &admin_pb.GetDefaultVerifyEmailMessageTextResponse{
+ CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
+ }, nil
+}
+
+func (s *Server) SetVerifyEmailMessageCustomText(ctx context.Context, req *admin_pb.SetDefaultVerifyEmailMessageTextRequest) (*admin_pb.SetDefaultVerifyEmailMessageTextResponse, error) {
+ result, err := s.command.SetDefaultMessageText(ctx, SetVerifyEmailCustomTextToDomain(req))
+ if err != nil {
+ return nil, err
+ }
+ return &admin_pb.SetDefaultVerifyEmailMessageTextResponse{
+ Details: object.ChangeToDetailsPb(
+ result.Sequence,
+ result.EventDate,
+ result.ResourceOwner,
+ ),
+ }, nil
+}
+
+func (s *Server) GetDefaultVerifyPhoneMessageText(ctx context.Context, req *admin_pb.GetDefaultVerifyPhoneMessageTextRequest) (*admin_pb.GetDefaultVerifyPhoneMessageTextResponse, error) {
+ msg, err := s.iam.GetDefaultMessageText(ctx, domain.VerifyPhoneMessageType, req.Language)
+ if err != nil {
+ return nil, err
+ }
+ return &admin_pb.GetDefaultVerifyPhoneMessageTextResponse{
+ CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
+ }, nil
+}
+
+func (s *Server) SetDefaultVerifyPhoneMessageText(ctx context.Context, req *admin_pb.SetDefaultVerifyPhoneMessageTextRequest) (*admin_pb.SetDefaultVerifyPhoneMessageTextResponse, error) {
+ result, err := s.command.SetDefaultMessageText(ctx, SetVerifyPhoneCustomTextToDomain(req))
+ if err != nil {
+ return nil, err
+ }
+ return &admin_pb.SetDefaultVerifyPhoneMessageTextResponse{
+ Details: object.ChangeToDetailsPb(
+ result.Sequence,
+ result.EventDate,
+ result.ResourceOwner,
+ ),
+ }, nil
+}
+
+func (s *Server) GetDefaultDomainClaimedMessageText(ctx context.Context, req *admin_pb.GetDefaultDomainClaimedMessageTextRequest) (*admin_pb.GetDefaultDomainClaimedMessageTextResponse, error) {
+ msg, err := s.iam.GetDefaultMessageText(ctx, domain.DomainClaimedMessageType, req.Language)
+ if err != nil {
+ return nil, err
+ }
+ return &admin_pb.GetDefaultDomainClaimedMessageTextResponse{
+ CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
+ }, nil
+}
+
+func (s *Server) SetDefaultDomainClaimedMessageText(ctx context.Context, req *admin_pb.SetDefaultDomainClaimedMessageTextRequest) (*admin_pb.SetDefaultDomainClaimedMessageTextResponse, error) {
+ result, err := s.command.SetDefaultMessageText(ctx, SetDomainClaimedCustomTextToDomain(req))
+ if err != nil {
+ return nil, err
+ }
+ return &admin_pb.SetDefaultDomainClaimedMessageTextResponse{
+ Details: object.ChangeToDetailsPb(
+ result.Sequence,
+ result.EventDate,
+ result.ResourceOwner,
+ ),
+ }, nil
+}
diff --git a/internal/api/grpc/admin/custom_text_converter.go b/internal/api/grpc/admin/custom_text_converter.go
new file mode 100644
index 0000000000..05f7afe278
--- /dev/null
+++ b/internal/api/grpc/admin/custom_text_converter.go
@@ -0,0 +1,83 @@
+package admin
+
+import (
+ "golang.org/x/text/language"
+
+ "github.com/caos/zitadel/internal/domain"
+ admin_pb "github.com/caos/zitadel/pkg/grpc/admin"
+)
+
+func SetInitCustomTextToDomain(msg *admin_pb.SetDefaultInitMessageTextRequest) *domain.CustomMessageText {
+ langTag := language.Make(msg.Language)
+ return &domain.CustomMessageText{
+ MessageTextType: domain.InitCodeMessageType,
+ Language: langTag,
+ Title: msg.Title,
+ PreHeader: msg.PreHeader,
+ Subject: msg.Subject,
+ Greeting: msg.Greeting,
+ Text: msg.Text,
+ ButtonText: msg.ButtonText,
+ FooterText: msg.FooterText,
+ }
+}
+
+func SetPasswordResetCustomTextToDomain(msg *admin_pb.SetDefaultPasswordResetMessageTextRequest) *domain.CustomMessageText {
+ langTag := language.Make(msg.Language)
+ return &domain.CustomMessageText{
+ MessageTextType: domain.PasswordResetMessageType,
+ Language: langTag,
+ Title: msg.Title,
+ PreHeader: msg.PreHeader,
+ Subject: msg.Subject,
+ Greeting: msg.Greeting,
+ Text: msg.Text,
+ ButtonText: msg.ButtonText,
+ FooterText: msg.FooterText,
+ }
+}
+
+func SetVerifyEmailCustomTextToDomain(msg *admin_pb.SetDefaultVerifyEmailMessageTextRequest) *domain.CustomMessageText {
+ langTag := language.Make(msg.Language)
+ return &domain.CustomMessageText{
+ MessageTextType: domain.VerifyEmailMessageType,
+ Language: langTag,
+ Title: msg.Title,
+ PreHeader: msg.PreHeader,
+ Subject: msg.Subject,
+ Greeting: msg.Greeting,
+ Text: msg.Text,
+ ButtonText: msg.ButtonText,
+ FooterText: msg.FooterText,
+ }
+}
+
+func SetVerifyPhoneCustomTextToDomain(msg *admin_pb.SetDefaultVerifyPhoneMessageTextRequest) *domain.CustomMessageText {
+ langTag := language.Make(msg.Language)
+ return &domain.CustomMessageText{
+ MessageTextType: domain.VerifyPhoneMessageType,
+ Language: langTag,
+ Title: msg.Title,
+ PreHeader: msg.PreHeader,
+ Subject: msg.Subject,
+ Greeting: msg.Greeting,
+ Text: msg.Text,
+ ButtonText: msg.ButtonText,
+ FooterText: msg.FooterText,
+ }
+}
+
+func SetDomainClaimedCustomTextToDomain(msg *admin_pb.SetDefaultDomainClaimedMessageTextRequest) *domain.CustomMessageText {
+ langTag := language.Make(msg.Language)
+ return &domain.CustomMessageText{
+ MessageTextType: domain.DomainClaimedMessageType,
+ Language: langTag,
+ Title: msg.Title,
+ PreHeader: msg.PreHeader,
+ Subject: msg.Subject,
+ Greeting: msg.Greeting,
+ Text: msg.Text,
+ ButtonText: msg.ButtonText,
+ FooterText: msg.FooterText,
+ }
+}
diff --git a/internal/api/grpc/admin/features.go b/internal/api/grpc/admin/features.go
index 3847857cef..21443cdb93 100644
--- a/internal/api/grpc/admin/features.go
+++ b/internal/api/grpc/admin/features.go
@@ -74,6 +74,7 @@ func setDefaultFeaturesRequestToDomain(req *admin_pb.SetDefaultFeaturesRequest)
LabelPolicyPrivateLabel: req.LabelPolicy || req.LabelPolicyPrivateLabel,
LabelPolicyWatermark: req.LabelPolicyWatermark,
CustomDomain: req.CustomDomain,
+ CustomText: req.CustomText,
}
}
@@ -94,5 +95,6 @@ func setOrgFeaturesRequestToDomain(req *admin_pb.SetOrgFeaturesRequest) *domain.
LabelPolicyPrivateLabel: req.LabelPolicy || req.LabelPolicyPrivateLabel,
LabelPolicyWatermark: req.LabelPolicyWatermark,
CustomDomain: req.CustomDomain,
+ CustomText: req.CustomText,
}
}
diff --git a/internal/api/grpc/features/features.go b/internal/api/grpc/features/features.go
index 51d1102385..b2b47f9956 100644
--- a/internal/api/grpc/features/features.go
+++ b/internal/api/grpc/features/features.go
@@ -27,6 +27,7 @@ func FeaturesFromModel(features *features_model.FeaturesView) *features_pb.Featu
CustomDomain: features.CustomDomain,
LabelPolicyPrivateLabel: features.LabelPolicyPrivateLabel,
LabelPolicyWatermark: features.LabelPolicyWatermark,
+ CustomText: features.CustomText,
}
}
diff --git a/internal/api/grpc/management/custom_text.go b/internal/api/grpc/management/custom_text.go
new file mode 100644
index 0000000000..c109f6c23a
--- /dev/null
+++ b/internal/api/grpc/management/custom_text.go
@@ -0,0 +1,131 @@
+package management
+
+import (
+ "context"
+
+ "github.com/caos/zitadel/internal/api/authz"
+ "github.com/caos/zitadel/internal/api/grpc/object"
+ text_grpc "github.com/caos/zitadel/internal/api/grpc/text"
+ "github.com/caos/zitadel/internal/domain"
+ mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
+)
+
+func (s *Server) GetCustomInitMessageText(ctx context.Context, req *mgmt_pb.GetCustomInitMessageTextRequest) (*mgmt_pb.GetCustomInitMessageTextResponse, error) {
+ msg, err := s.org.GetMessageText(ctx, authz.GetCtxData(ctx).OrgID, domain.InitCodeMessageType, req.Language)
+ if err != nil {
+ return nil, err
+ }
+ return &mgmt_pb.GetCustomInitMessageTextResponse{
+ CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
+ }, nil
+}
+
+func (s *Server) SetCustomInitMessageText(ctx context.Context, req *mgmt_pb.SetCustomInitMessageTextRequest) (*mgmt_pb.SetCustomInitMessageTextResponse, error) {
+ result, err := s.command.SetOrgMessageText(ctx, authz.GetCtxData(ctx).OrgID, SetInitCustomTextToDomain(req))
+ if err != nil {
+ return nil, err
+ }
+ return &mgmt_pb.SetCustomInitMessageTextResponse{
+ Details: object.ChangeToDetailsPb(
+ result.Sequence,
+ result.EventDate,
+ result.ResourceOwner,
+ ),
+ }, nil
+}
+
+func (s *Server) GetCustomPasswordResetMessageText(ctx context.Context, req *mgmt_pb.GetCustomPasswordResetMessageTextRequest) (*mgmt_pb.GetCustomPasswordResetMessageTextResponse, error) {
+ msg, err := s.org.GetMessageText(ctx, authz.GetCtxData(ctx).OrgID, domain.PasswordResetMessageType, req.Language)
+ if err != nil {
+ return nil, err
+ }
+ return &mgmt_pb.GetCustomPasswordResetMessageTextResponse{
+ CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
+ }, nil
+}
+
+func (s *Server) SetCustomPasswordResetMessageText(ctx context.Context, req *mgmt_pb.SetCustomPasswordResetMessageTextRequest) (*mgmt_pb.SetCustomPasswordResetMessageTextResponse, error) {
+ result, err := s.command.SetOrgMessageText(ctx, authz.GetCtxData(ctx).OrgID, SetPasswordResetCustomTextToDomain(req))
+ if err != nil {
+ return nil, err
+ }
+ return &mgmt_pb.SetCustomPasswordResetMessageTextResponse{
+ Details: object.ChangeToDetailsPb(
+ result.Sequence,
+ result.EventDate,
+ result.ResourceOwner,
+ ),
+ }, nil
+}
+
+func (s *Server) GetCustomVerifyEmailMessageText(ctx context.Context, req *mgmt_pb.GetCustomVerifyEmailMessageTextRequest) (*mgmt_pb.GetCustomVerifyEmailMessageTextResponse, error) {
+ msg, err := s.org.GetMessageText(ctx, authz.GetCtxData(ctx).OrgID, domain.VerifyEmailMessageType, req.Language)
+ if err != nil {
+ return nil, err
+ }
+ return &mgmt_pb.GetCustomVerifyEmailMessageTextResponse{
+ CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
+ }, nil
+}
+
+func (s *Server) SetCustomVerifyEmailMessageText(ctx context.Context, req *mgmt_pb.SetCustomVerifyEmailMessageTextRequest) (*mgmt_pb.SetCustomVerifyEmailMessageTextResponse, error) {
+ result, err := s.command.SetOrgMessageText(ctx, authz.GetCtxData(ctx).OrgID, SetVerifyEmailCustomTextToDomain(req))
+ if err != nil {
+ return nil, err
+ }
+ return &mgmt_pb.SetCustomVerifyEmailMessageTextResponse{
+ Details: object.ChangeToDetailsPb(
+ result.Sequence,
+ result.EventDate,
+ result.ResourceOwner,
+ ),
+ }, nil
+}
+
+func (s *Server) GetCustomVerifyPhoneMessageText(ctx context.Context, req *mgmt_pb.GetCustomVerifyPhoneMessageTextRequest) (*mgmt_pb.GetCustomVerifyPhoneMessageTextResponse, error) {
+ msg, err := s.org.GetMessageText(ctx, authz.GetCtxData(ctx).OrgID, domain.VerifyPhoneMessageType, req.Language)
+ if err != nil {
+ return nil, err
+ }
+ return &mgmt_pb.GetCustomVerifyPhoneMessageTextResponse{
+ CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
+ }, nil
+}
+
+func (s *Server) SetCustomVerifyPhoneMessageText(ctx context.Context, req *mgmt_pb.SetCustomVerifyPhoneMessageTextRequest) (*mgmt_pb.SetCustomVerifyPhoneMessageTextResponse, error) {
+ result, err := s.command.SetOrgMessageText(ctx, authz.GetCtxData(ctx).OrgID, SetVerifyPhoneCustomTextToDomain(req))
+ if err != nil {
+ return nil, err
+ }
+ return &mgmt_pb.SetCustomVerifyPhoneMessageTextResponse{
+ Details: object.ChangeToDetailsPb(
+ result.Sequence,
+ result.EventDate,
+ result.ResourceOwner,
+ ),
+ }, nil
+}
+
+func (s *Server) GetCustomDomainClaimedMessageText(ctx context.Context, req *mgmt_pb.GetCustomDomainClaimedMessageTextRequest) (*mgmt_pb.GetCustomDomainClaimedMessageTextResponse, error) {
+ msg, err := s.org.GetMessageText(ctx, authz.GetCtxData(ctx).OrgID, domain.DomainClaimedMessageType, req.Language)
+ if err != nil {
+ return nil, err
+ }
+ return &mgmt_pb.GetCustomDomainClaimedMessageTextResponse{
+ CustomText: text_grpc.ModelCustomMsgTextToPb(msg),
+ }, nil
+}
+
+func (s *Server) SetCustomDomainClaimedMessageText(ctx context.Context, req *mgmt_pb.SetCustomDomainClaimedMessageTextRequest) (*mgmt_pb.SetCustomDomainClaimedMessageTextResponse, error) {
+ result, err := s.command.SetOrgMessageText(ctx, authz.GetCtxData(ctx).OrgID, SetDomainClaimedCustomTextToDomain(req))
+ if err != nil {
+ return nil, err
+ }
+ return &mgmt_pb.SetCustomDomainClaimedMessageTextResponse{
+ Details: object.ChangeToDetailsPb(
+ result.Sequence,
+ result.EventDate,
+ result.ResourceOwner,
+ ),
+ }, nil
+}
diff --git a/internal/api/grpc/management/custom_text_converter.go b/internal/api/grpc/management/custom_text_converter.go
new file mode 100644
index 0000000000..1a3d09c35b
--- /dev/null
+++ b/internal/api/grpc/management/custom_text_converter.go
@@ -0,0 +1,83 @@
+package management
+
+import (
+ "golang.org/x/text/language"
+
+ "github.com/caos/zitadel/internal/domain"
+ mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
+)
+
+func SetInitCustomTextToDomain(msg *mgmt_pb.SetCustomInitMessageTextRequest) *domain.CustomMessageText {
+ langTag := language.Make(msg.Language)
+ return &domain.CustomMessageText{
+ MessageTextType: domain.InitCodeMessageType,
+ Language: langTag,
+ Title: msg.Title,
+ PreHeader: msg.PreHeader,
+ Subject: msg.Subject,
+ Greeting: msg.Greeting,
+ Text: msg.Text,
+ ButtonText: msg.ButtonText,
+ FooterText: msg.FooterText,
+ }
+}
+
+func SetPasswordResetCustomTextToDomain(msg *mgmt_pb.SetCustomPasswordResetMessageTextRequest) *domain.CustomMessageText {
+ langTag := language.Make(msg.Language)
+ return &domain.CustomMessageText{
+ MessageTextType: domain.PasswordResetMessageType,
+ Language: langTag,
+ Title: msg.Title,
+ PreHeader: msg.PreHeader,
+ Subject: msg.Subject,
+ Greeting: msg.Greeting,
+ Text: msg.Text,
+ ButtonText: msg.ButtonText,
+ FooterText: msg.FooterText,
+ }
+}
+
+func SetVerifyEmailCustomTextToDomain(msg *mgmt_pb.SetCustomVerifyEmailMessageTextRequest) *domain.CustomMessageText {
+ langTag := language.Make(msg.Language)
+ return &domain.CustomMessageText{
+ MessageTextType: domain.VerifyEmailMessageType,
+ Language: langTag,
+ Title: msg.Title,
+ PreHeader: msg.PreHeader,
+ Subject: msg.Subject,
+ Greeting: msg.Greeting,
+ Text: msg.Text,
+ ButtonText: msg.ButtonText,
+ FooterText: msg.FooterText,
+ }
+}
+
+func SetVerifyPhoneCustomTextToDomain(msg *mgmt_pb.SetCustomVerifyPhoneMessageTextRequest) *domain.CustomMessageText {
+ langTag := language.Make(msg.Language)
+ return &domain.CustomMessageText{
+ MessageTextType: domain.VerifyPhoneMessageType,
+ Language: langTag,
+ Title: msg.Title,
+ PreHeader: msg.PreHeader,
+ Subject: msg.Subject,
+ Greeting: msg.Greeting,
+ Text: msg.Text,
+ ButtonText: msg.ButtonText,
+ FooterText: msg.FooterText,
+ }
+}
+
+func SetDomainClaimedCustomTextToDomain(msg *mgmt_pb.SetCustomDomainClaimedMessageTextRequest) *domain.CustomMessageText {
+ langTag := language.Make(msg.Language)
+ return &domain.CustomMessageText{
+ MessageTextType: domain.DomainClaimedMessageType,
+ Language: langTag,
+ Title: msg.Title,
+ PreHeader: msg.PreHeader,
+ Subject: msg.Subject,
+ Greeting: msg.Greeting,
+ Text: msg.Text,
+ ButtonText: msg.ButtonText,
+ FooterText: msg.FooterText,
+ }
+}
diff --git a/internal/api/grpc/text/custom_text.go b/internal/api/grpc/text/custom_text.go
new file mode 100644
index 0000000000..aea81e56b5
--- /dev/null
+++ b/internal/api/grpc/text/custom_text.go
@@ -0,0 +1,25 @@
+package text
+
+import (
+ "github.com/caos/zitadel/internal/api/grpc/object"
+ "github.com/caos/zitadel/internal/iam/model"
+ text_pb "github.com/caos/zitadel/pkg/grpc/text"
+)
+
+func ModelCustomMsgTextToPb(msg *model.MessageTextView) *text_pb.MessageCustomText {
+ return &text_pb.MessageCustomText{
+ Title: msg.Title,
+ PreHeader: msg.PreHeader,
+ Subject: msg.Subject,
+ Greeting: msg.Greeting,
+ Text: msg.Text,
+ ButtonText: msg.ButtonText,
+ FooterText: msg.FooterText,
+ Details: object.ToViewDetailsPb(
+ msg.Sequence,
+ msg.CreationDate,
+ msg.ChangeDate,
+ "", //TODO: resourceowner
+ ),
+ }
+}
diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go
index c64a9a6aa7..c6ccd12ff5 100644
--- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go
+++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go
@@ -465,7 +465,9 @@ func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *domain
if err != nil {
return err
}
-
+ if user.State == int32(domain.UserStateInactive) {
+ return errors.ThrowPreconditionFailed(nil, "AUTH-2n8fs", "Errors.User.Inactive")
+ }
request.SetUserInfo(user.ID, loginName, user.PreferredLoginName, "", "", user.ResourceOwner)
return nil
}
diff --git a/internal/authz/repository/eventsourcing/eventstore/token_verifier.go b/internal/authz/repository/eventsourcing/eventstore/token_verifier.go
index c0ebde5d95..862e298678 100644
--- a/internal/authz/repository/eventsourcing/eventstore/token_verifier.go
+++ b/internal/authz/repository/eventsourcing/eventstore/token_verifier.go
@@ -145,6 +145,18 @@ func checkFeatures(features *features_view_model.FeaturesView, requiredFeatures
}
continue
}
+ if requiredFeature == domain.FeatureCustomDomain {
+ if !features.CustomDomain {
+ return MissingFeatureErr(requiredFeature)
+ }
+ continue
+ }
+ if requiredFeature == domain.FeatureCustomText {
+ if !features.CustomText {
+ return MissingFeatureErr(requiredFeature)
+ }
+ continue
+ }
return MissingFeatureErr(requiredFeature)
}
return nil
diff --git a/internal/command/custom_message_text_model.go b/internal/command/custom_message_text_model.go
new file mode 100644
index 0000000000..225b4fbf0d
--- /dev/null
+++ b/internal/command/custom_message_text_model.go
@@ -0,0 +1,167 @@
+package command
+
+import (
+ "golang.org/x/text/language"
+
+ "github.com/caos/zitadel/internal/domain"
+ "github.com/caos/zitadel/internal/eventstore"
+ "github.com/caos/zitadel/internal/repository/policy"
+)
+
+type CustomMessageTextReadModel struct {
+ eventstore.WriteModel
+
+ MessageTextType string
+ Language language.Tag
+ Title string
+ PreHeader string
+ Subject string
+ Greeting string
+ Text string
+ ButtonText string
+ FooterText string
+
+ State domain.PolicyState
+}
+
+func (wm *CustomMessageTextReadModel) Reduce() error {
+ for _, event := range wm.Events {
+ switch e := event.(type) {
+ case *policy.CustomTextSetEvent:
+ if e.Template != wm.MessageTextType || wm.Language != e.Language {
+ continue
+ }
+ if e.Key == domain.MessageSubject {
+ wm.Subject = e.Text
+ }
+ if e.Key == domain.MessageTitle {
+ wm.Title = e.Text
+ }
+ if e.Key == domain.MessagePreHeader {
+ wm.PreHeader = e.Text
+ }
+ if e.Key == domain.MessageText {
+ wm.Text = e.Text
+ }
+ if e.Key == domain.MessageGreeting {
+ wm.Greeting = e.Text
+ }
+ if e.Key == domain.MessageButtonText {
+ wm.ButtonText = e.Text
+ }
+ if e.Key == domain.MessageFooterText {
+ wm.FooterText = e.Text
+ }
+ wm.State = domain.PolicyStateActive
+ case *policy.CustomTextRemovedEvent:
+ if e.Key != wm.MessageTextType || wm.Language != e.Language {
+ continue
+ }
+ if e.Key == domain.MessageSubject {
+ wm.Subject = ""
+ }
+ if e.Key == domain.MessageTitle {
+ wm.Title = ""
+ }
+ if e.Key == domain.MessagePreHeader {
+ wm.PreHeader = ""
+ }
+ if e.Key == domain.MessageText {
+ wm.Text = ""
+ }
+ if e.Key == domain.MessageGreeting {
+ wm.Greeting = ""
+ }
+ if e.Key == domain.MessageButtonText {
+ wm.ButtonText = ""
+ }
+ if e.Key == domain.MessageFooterText {
+ wm.FooterText = ""
+ }
+ case *policy.CustomTextTemplateRemovedEvent:
+ wm.State = domain.PolicyStateRemoved
+ }
+ }
+ return wm.WriteModel.Reduce()
+}
+
+type CustomMessageTemplatesReadModel struct {
+ eventstore.WriteModel
+ CustomMessageTemplate map[string]*CustomText
+}
+
+type CustomText struct {
+ Template string
+ Language language.Tag
+ Title string
+ PreHeader string
+ Subject string
+ Greeting string
+ Text string
+ ButtonText string
+ FooterText string
+ State domain.PolicyState
+}
+
+func (wm *CustomMessageTemplatesReadModel) Reduce() error {
+ for _, event := range wm.Events {
+ switch e := event.(type) {
+ case *policy.CustomTextSetEvent:
+ if _, ok := wm.CustomMessageTemplate[e.Template+e.Language.String()]; !ok {
+ wm.CustomMessageTemplate[e.Template+e.Language.String()] = &CustomText{Language: e.Language, Template: e.Template}
+ }
+ if e.Key == domain.MessageSubject {
+ wm.CustomMessageTemplate[e.Template+e.Language.String()].Subject = e.Text
+ }
+ if e.Key == domain.MessageTitle {
+ wm.CustomMessageTemplate[e.Template+e.Language.String()].Title = e.Text
+ }
+ if e.Key == domain.MessagePreHeader {
+ wm.CustomMessageTemplate[e.Template+e.Language.String()].PreHeader = e.Text
+ }
+ if e.Key == domain.MessageText {
+ wm.CustomMessageTemplate[e.Template+e.Language.String()].Text = e.Text
+ }
+ if e.Key == domain.MessageGreeting {
+ wm.CustomMessageTemplate[e.Template+e.Language.String()].Greeting = e.Text
+ }
+ if e.Key == domain.MessageButtonText {
+ wm.CustomMessageTemplate[e.Template+e.Language.String()].ButtonText = e.Text
+ }
+ if e.Key == domain.MessageFooterText {
+ wm.CustomMessageTemplate[e.Template+e.Language.String()].FooterText = e.Text
+ }
+ wm.CustomMessageTemplate[e.Template+e.Language.String()].State = domain.PolicyStateActive
+ case *policy.CustomTextRemovedEvent:
+ if _, ok := wm.CustomMessageTemplate[e.Template+e.Language.String()]; !ok {
+ wm.CustomMessageTemplate[e.Template+e.Language.String()] = new(CustomText)
+ }
+ if e.Key == domain.MessageSubject {
+ wm.CustomMessageTemplate[e.Template+e.Language.String()].Subject = ""
+ }
+ if e.Key == domain.MessageTitle {
+ wm.CustomMessageTemplate[e.Template+e.Language.String()].Title = ""
+ }
+ if e.Key == domain.MessagePreHeader {
+ wm.CustomMessageTemplate[e.Template+e.Language.String()].PreHeader = ""
+ }
+ if e.Key == domain.MessageText {
+ wm.CustomMessageTemplate[e.Template+e.Language.String()].Text = ""
+ }
+ if e.Key == domain.MessageGreeting {
+ wm.CustomMessageTemplate[e.Template+e.Language.String()].Greeting = ""
+ }
+ if e.Key == domain.MessageButtonText {
+ wm.CustomMessageTemplate[e.Template+e.Language.String()].ButtonText = ""
+ }
+ if e.Key == domain.MessageFooterText {
+ wm.CustomMessageTemplate[e.Template+e.Language.String()].FooterText = ""
+ }
+ case *policy.CustomTextTemplateRemovedEvent:
+ if _, ok := wm.CustomMessageTemplate[e.Template+e.Language.String()]; ok {
+ delete(wm.CustomMessageTemplate, e.Template)
+ }
+ }
+ }
+ return wm.WriteModel.Reduce()
+}
diff --git a/internal/command/custom_text_model.go b/internal/command/custom_text_model.go
new file mode 100644
index 0000000000..152e330977
--- /dev/null
+++ b/internal/command/custom_text_model.go
@@ -0,0 +1,34 @@
+package command
+
+import (
+ "golang.org/x/text/language"
+
+ "github.com/caos/zitadel/internal/domain"
+ "github.com/caos/zitadel/internal/eventstore"
+ "github.com/caos/zitadel/internal/repository/policy"
+)
+
+type CustomTextWriteModel struct {
+ eventstore.WriteModel
+
+ Key string
+ Language language.Tag
+ Text string
+ State domain.CustomTextState
+}
+
+func (wm *CustomTextWriteModel) Reduce() error {
+ for _, event := range wm.Events {
+ switch e := event.(type) {
+ case *policy.CustomTextSetEvent:
+ if wm.Key != e.Key || wm.Language != e.Language {
+ continue
+ }
+ wm.Text = e.Text
+ wm.State = domain.CustomTextStateActive
+ case *policy.CustomTextRemovedEvent:
+ wm.State = domain.CustomTextStateRemoved
+ }
+ }
+ return wm.WriteModel.Reduce()
+}
diff --git a/internal/command/features_model.go b/internal/command/features_model.go
index ba4276ad31..62567a6f2e 100644
--- a/internal/command/features_model.go
+++ b/internal/command/features_model.go
@@ -26,6 +26,7 @@ type FeaturesWriteModel struct {
LabelPolicyPrivateLabel bool
LabelPolicyWatermark bool
CustomDomain bool
+ CustomText bool
}
func (wm *FeaturesWriteModel) Reduce() error {
@@ -81,6 +82,9 @@ func (wm *FeaturesWriteModel) Reduce() error {
if e.CustomDomain != nil {
wm.CustomDomain = *e.CustomDomain
}
+ if e.CustomText != nil {
+ wm.CustomText = *e.CustomText
+ }
case *features.FeaturesRemovedEvent:
wm.State = domain.FeaturesStateRemoved
}
diff --git a/internal/command/iam_converter.go b/internal/command/iam_converter.go
index e290260772..f1fcf3111a 100644
--- a/internal/command/iam_converter.go
+++ b/internal/command/iam_converter.go
@@ -69,21 +69,6 @@ func writeModelToMailTemplate(wm *MailTemplateWriteModel) *domain.MailTemplate {
}
}
-func writeModelToMailText(wm *MailTextWriteModel) *domain.MailText {
- return &domain.MailText{
- ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
- MailTextType: wm.MailTextType,
- Language: wm.Language,
- Title: wm.Title,
- PreHeader: wm.PreHeader,
- Subject: wm.Subject,
- Greeting: wm.Greeting,
- Text: wm.Text,
- ButtonText: wm.ButtonText,
- State: wm.State,
- }
-}
-
func writeModelToOrgIAMPolicy(wm *IAMOrgIAMPolicyWriteModel) *domain.OrgIAMPolicy {
return &domain.OrgIAMPolicy{
ObjectRoot: writeModelToObjectRoot(wm.PolicyOrgIAMWriteModel.WriteModel),
@@ -98,18 +83,13 @@ func writeModelToMailTemplatePolicy(wm *MailTemplateWriteModel) *domain.MailTemp
}
}
-func writeModelToMailTextPolicy(wm *MailTextWriteModel) *domain.MailText {
- return &domain.MailText{
- ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
- State: wm.State,
- MailTextType: wm.MailTextType,
- Language: wm.Language,
- Title: wm.Title,
- PreHeader: wm.PreHeader,
- Subject: wm.Subject,
- Greeting: wm.Greeting,
- Text: wm.Text,
- ButtonText: wm.ButtonText,
+func writeModelToCustomText(wm *CustomTextWriteModel) *domain.CustomText {
+ return &domain.CustomText{
+ ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
+ State: wm.State,
+ Key: wm.Key,
+ Language: wm.Language,
+ Text: wm.Text,
}
}
diff --git a/internal/command/iam_custom_message_text.go b/internal/command/iam_custom_message_text.go
new file mode 100644
index 0000000000..96be6c8af9
--- /dev/null
+++ b/internal/command/iam_custom_message_text.go
@@ -0,0 +1,72 @@
+package command
+
+import (
+ "context"
+
+ "golang.org/x/text/language"
+
+ "github.com/caos/zitadel/internal/domain"
+ caos_errs "github.com/caos/zitadel/internal/errors"
+ "github.com/caos/zitadel/internal/eventstore"
+ "github.com/caos/zitadel/internal/repository/iam"
+)
+
+func (c *Commands) SetDefaultMessageText(ctx context.Context, messageText *domain.CustomMessageText) (*domain.ObjectDetails, error) {
+ iamAgg := iam.NewAggregate()
+ events, existingMessageText, err := c.setDefaultMessageText(ctx, &iamAgg.Aggregate, messageText)
+ if err != nil {
+ return nil, err
+ }
+ pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
+ if err != nil {
+ return nil, err
+ }
+ err = AppendAndReduce(existingMessageText, pushedEvents...)
+ if err != nil {
+ return nil, err
+ }
+ return writeModelToObjectDetails(&existingMessageText.WriteModel), nil
+}
+
+func (c *Commands) setDefaultMessageText(ctx context.Context, iamAgg *eventstore.Aggregate, msg *domain.CustomMessageText) ([]eventstore.EventPusher, *IAMCustomMessageTextReadModel, error) {
+ if !msg.IsValid() {
+ return nil, nil, caos_errs.ThrowInvalidArgument(nil, "IAM-kd9fs", "Errors.CustomMessageText.Invalid")
+ }
+
+ existingMessageText, err := c.defaultCustomMessageTextWriteModelByID(ctx, msg.MessageTextType, msg.Language)
+ if err != nil {
+ return nil, nil, err
+ }
+ events := make([]eventstore.EventPusher, 0)
+ if existingMessageText.Greeting != msg.Greeting {
+ events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageGreeting, msg.Greeting, msg.Language))
+ }
+ if existingMessageText.Subject != msg.Subject {
+ events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageSubject, msg.Subject, msg.Language))
+ }
+ if existingMessageText.Title != msg.Title {
+ events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageTitle, msg.Title, msg.Language))
+ }
+ if existingMessageText.PreHeader != msg.PreHeader {
+ events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessagePreHeader, msg.PreHeader, msg.Language))
+ }
+ if existingMessageText.Text != msg.Text {
+ events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageText, msg.Text, msg.Language))
+ }
+ if existingMessageText.ButtonText != msg.ButtonText {
+ events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageButtonText, msg.ButtonText, msg.Language))
+ }
+ if existingMessageText.FooterText != msg.FooterText {
+ events = append(events, iam.NewCustomTextSetEvent(ctx, iamAgg, msg.MessageTextType, domain.MessageFooterText, msg.FooterText, msg.Language))
+ }
+ return events, existingMessageText, nil
+}
+
+func (c *Commands) defaultCustomMessageTextWriteModelByID(ctx context.Context, messageType string, lang language.Tag) (*IAMCustomMessageTextReadModel, error) {
+ writeModel := NewIAMCustomMessageTextWriteModel(messageType, lang)
+ err := c.eventstore.FilterToQueryReducer(ctx, writeModel)
+ if err != nil {
+ return nil, err
+ }
+ return writeModel, nil
+}
diff --git a/internal/command/iam_custom_message_text_model.go b/internal/command/iam_custom_message_text_model.go
new file mode 100644
index 0000000000..dce661e3ce
--- /dev/null
+++ b/internal/command/iam_custom_message_text_model.go
@@ -0,0 +1,47 @@
+package command
+
+import (
+ "golang.org/x/text/language"
+
+ "github.com/caos/zitadel/internal/domain"
+ "github.com/caos/zitadel/internal/eventstore"
+ "github.com/caos/zitadel/internal/repository/iam"
+)
+
+type IAMCustomMessageTextReadModel struct {
+ CustomMessageTextReadModel
+}
+
+func NewIAMCustomMessageTextWriteModel(messageTextType string, lang language.Tag) *IAMCustomMessageTextReadModel {
+ return &IAMCustomMessageTextReadModel{
+ CustomMessageTextReadModel{
+ WriteModel: eventstore.WriteModel{
+ AggregateID: domain.IAMID,
+ ResourceOwner: domain.IAMID,
+ },
+ MessageTextType: messageTextType,
+ Language: lang,
+ },
+ }
+}
+
+func (wm *IAMCustomMessageTextReadModel) AppendEvents(events ...eventstore.EventReader) {
+ for _, event := range events {
+ switch e := event.(type) {
+ case *iam.CustomTextSetEvent:
+ wm.CustomMessageTextReadModel.AppendEvents(&e.CustomTextSetEvent)
+ }
+ }
+}
+
+func (wm *IAMCustomMessageTextReadModel) Reduce() error {
+ return wm.CustomMessageTextReadModel.Reduce()
+}
+
+func (wm *IAMCustomMessageTextReadModel) Query() *eventstore.SearchQueryBuilder {
+ return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, iam.AggregateType).
+ AggregateIDs(wm.CustomMessageTextReadModel.AggregateID).
+ ResourceOwner(wm.ResourceOwner).
+ EventTypes(
+ iam.CustomTextSetEventType)
+}
diff --git a/internal/command/iam_custom_message_text_test.go b/internal/command/iam_custom_message_text_test.go
new file mode 100644
index 0000000000..910e6398b3
--- /dev/null
+++ b/internal/command/iam_custom_message_text_test.go
@@ -0,0 +1,163 @@
+package command
+
+import (
+ "context"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "golang.org/x/text/language"
+
+ "github.com/caos/zitadel/internal/domain"
+ caos_errs "github.com/caos/zitadel/internal/errors"
+ "github.com/caos/zitadel/internal/eventstore"
+ "github.com/caos/zitadel/internal/eventstore/repository"
+ "github.com/caos/zitadel/internal/repository/iam"
+)
+
+func TestCommandSide_SetDefaultMessageText(t *testing.T) {
+ type fields struct {
+ eventstore *eventstore.Eventstore
+ }
+ type args struct {
+ ctx context.Context
+ config *domain.CustomMessageText
+ }
+ type res struct {
+ want *domain.ObjectDetails
+ err func(error) bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ res res
+ }{
+ {
+ name: "invalid custom text, error",
+ fields: fields{
+ eventstore: eventstoreExpect(
+ t,
+ ),
+ },
+ args: args{
+ ctx: context.Background(),
+ config: &domain.CustomMessageText{},
+ },
+ res: res{
+ err: caos_errs.IsErrorInvalidArgument,
+ },
+ },
+ {
+ name: "custom text set all fields, ok",
+ fields: fields{
+ eventstore: eventstoreExpect(
+ t,
+ expectFilter(),
+ expectPush(
+ []*repository.Event{
+ eventFromEventPusher(
+ iam.NewCustomTextSetEvent(context.Background(),
+ &iam.NewAggregate().Aggregate,
+ "Template",
+ domain.MessageGreeting,
+ "Greeting",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ iam.NewCustomTextSetEvent(context.Background(),
+ &iam.NewAggregate().Aggregate,
+ "Template",
+ domain.MessageSubject,
+ "Subject",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ iam.NewCustomTextSetEvent(context.Background(),
+ &iam.NewAggregate().Aggregate,
+ "Template",
+ domain.MessageTitle,
+ "Title",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ iam.NewCustomTextSetEvent(context.Background(),
+ &iam.NewAggregate().Aggregate,
+ "Template",
+ domain.MessagePreHeader,
+ "PreHeader",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ iam.NewCustomTextSetEvent(context.Background(),
+ &iam.NewAggregate().Aggregate,
+ "Template",
+ domain.MessageText,
+ "Text",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ iam.NewCustomTextSetEvent(context.Background(),
+ &iam.NewAggregate().Aggregate,
+ "Template",
+ domain.MessageButtonText,
+ "ButtonText",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ iam.NewCustomTextSetEvent(context.Background(),
+ &iam.NewAggregate().Aggregate,
+ "Template",
+ domain.MessageFooterText,
+ "FooterText",
+ language.English,
+ ),
+ ),
+ },
+ ),
+ ),
+ },
+ args: args{
+ ctx: context.Background(),
+ config: &domain.CustomMessageText{
+ MessageTextType: "Template",
+ Language: language.English,
+ Greeting: "Greeting",
+ Subject: "Subject",
+ Title: "Title",
+ PreHeader: "PreHeader",
+ Text: "Text",
+ ButtonText: "ButtonText",
+ FooterText: "FooterText",
+ },
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "IAM",
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ r := &Commands{
+ eventstore: tt.fields.eventstore,
+ }
+ got, err := r.SetDefaultMessageText(tt.args.ctx, tt.args.config)
+ if tt.res.err == nil {
+ assert.NoError(t, err)
+ }
+ if tt.res.err != nil && !tt.res.err(err) {
+ t.Errorf("got wrong err: %v ", err)
+ }
+ if tt.res.err == nil {
+ assert.Equal(t, tt.res.want, got)
+ }
+ })
+ }
+}
diff --git a/internal/command/iam_features.go b/internal/command/iam_features.go
index a63ce39883..c24eebfa96 100644
--- a/internal/command/iam_features.go
+++ b/internal/command/iam_features.go
@@ -49,6 +49,7 @@ func (c *Commands) setDefaultFeatures(ctx context.Context, existingFeatures *IAM
features.LabelPolicyPrivateLabel,
features.LabelPolicyWatermark,
features.CustomDomain,
+ features.CustomText,
)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")
diff --git a/internal/command/iam_features_model.go b/internal/command/iam_features_model.go
index 8d47bab357..9b1042b441 100644
--- a/internal/command/iam_features_model.go
+++ b/internal/command/iam_features_model.go
@@ -64,7 +64,8 @@ func (wm *IAMFeaturesWriteModel) NewSetEvent(
passwordComplexityPolicy,
labelPolicyPrivateLabel,
labelPolicyWatermark,
- customDomain bool,
+ customDomain,
+ customText bool,
) (*iam.FeaturesSetEvent, bool) {
changes := make([]features.FeaturesChanges, 0)
@@ -111,6 +112,9 @@ func (wm *IAMFeaturesWriteModel) NewSetEvent(
if wm.CustomDomain != customDomain {
changes = append(changes, features.ChangeCustomDomain(customDomain))
}
+ if wm.CustomText != customText {
+ changes = append(changes, features.ChangeCustomText(customText))
+ }
if len(changes) == 0 {
return nil, false
diff --git a/internal/command/iam_policy_mail_text.go b/internal/command/iam_policy_mail_text.go
deleted file mode 100644
index c65f7f21a9..0000000000
--- a/internal/command/iam_policy_mail_text.go
+++ /dev/null
@@ -1,106 +0,0 @@
-package command
-
-import (
- "context"
- "github.com/caos/zitadel/internal/domain"
- caos_errs "github.com/caos/zitadel/internal/errors"
- "github.com/caos/zitadel/internal/eventstore"
- iam_repo "github.com/caos/zitadel/internal/repository/iam"
- "github.com/caos/zitadel/internal/telemetry/tracing"
-)
-
-func (c *Commands) AddDefaultMailText(ctx context.Context, policy *domain.MailText) (*domain.MailText, error) {
- addedPolicy := NewIAMMailTextWriteModel(policy.MailTextType, policy.Language)
- iamAgg := IAMAggregateFromWriteModel(&addedPolicy.MailTextWriteModel.WriteModel)
- event, err := c.addDefaultMailText(ctx, iamAgg, addedPolicy, policy)
- if err != nil {
- return nil, err
- }
-
- pushedEvents, err := c.eventstore.PushEvents(ctx, event)
- if err != nil {
- return nil, err
- }
- err = AppendAndReduce(addedPolicy, pushedEvents...)
- if err != nil {
- return nil, err
- }
- return writeModelToMailTextPolicy(&addedPolicy.MailTextWriteModel), nil
-}
-
-func (c *Commands) addDefaultMailText(ctx context.Context, iamAgg *eventstore.Aggregate, addedPolicy *IAMMailTextWriteModel, mailText *domain.MailText) (eventstore.EventPusher, error) {
- if !mailText.IsValid() {
- return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-3n8fs", "Errors.IAM.MailText.Invalid")
- }
- err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
- if err != nil {
- return nil, err
- }
- if addedPolicy.State == domain.PolicyStateActive {
- return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-9o0pM", "Errors.IAM.MailText.AlreadyExists")
- }
-
- return iam_repo.NewMailTextAddedEvent(
- ctx,
- iamAgg,
- mailText.MailTextType,
- mailText.Language,
- mailText.Title,
- mailText.PreHeader,
- mailText.Subject,
- mailText.Greeting,
- mailText.Text,
- mailText.ButtonText), nil
-}
-
-func (c *Commands) ChangeDefaultMailText(ctx context.Context, mailText *domain.MailText) (*domain.MailText, error) {
- if !mailText.IsValid() {
- return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-kd9fs", "Errors.IAM.MailText.Invalid")
- }
- existingPolicy, err := c.defaultMailTextWriteModelByID(ctx, mailText.MailTextType, mailText.Language)
- if err != nil {
- return nil, err
- }
-
- if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
- return nil, caos_errs.ThrowNotFound(nil, "IAM-2N8fs", "Errors.IAM.MailText.NotFound")
- }
-
- iamAgg := IAMAggregateFromWriteModel(&existingPolicy.MailTextWriteModel.WriteModel)
- changedEvent, hasChanged := existingPolicy.NewChangedEvent(
- ctx,
- iamAgg,
- mailText.MailTextType,
- mailText.Language,
- mailText.Title,
- mailText.PreHeader,
- mailText.Subject,
- mailText.Greeting,
- mailText.Text,
- mailText.ButtonText)
- if !hasChanged {
- return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-m9L0s", "Errors.IAM.MailText.NotChanged")
- }
-
- pushedEvents, err := c.eventstore.PushEvents(ctx, changedEvent)
- if err != nil {
- return nil, err
- }
- err = AppendAndReduce(existingPolicy, pushedEvents...)
- if err != nil {
- return nil, err
- }
- return writeModelToMailTextPolicy(&existingPolicy.MailTextWriteModel), nil
-}
-
-func (c *Commands) defaultMailTextWriteModelByID(ctx context.Context, mailTextType, language string) (policy *IAMMailTextWriteModel, err error) {
- ctx, span := tracing.NewSpan(ctx)
- defer func() { span.EndWithError(err) }()
-
- writeModel := NewIAMMailTextWriteModel(mailTextType, language)
- err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
- if err != nil {
- return nil, err
- }
- return writeModel, nil
-}
diff --git a/internal/command/iam_policy_mail_text_model.go b/internal/command/iam_policy_mail_text_model.go
deleted file mode 100644
index 6f18224517..0000000000
--- a/internal/command/iam_policy_mail_text_model.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package command
-
-import (
- "context"
- "github.com/caos/zitadel/internal/domain"
- "github.com/caos/zitadel/internal/eventstore"
- "github.com/caos/zitadel/internal/repository/iam"
- "github.com/caos/zitadel/internal/repository/policy"
-)
-
-type IAMMailTextWriteModel struct {
- MailTextWriteModel
-}
-
-func NewIAMMailTextWriteModel(mailTextType, language string) *IAMMailTextWriteModel {
- return &IAMMailTextWriteModel{
- MailTextWriteModel{
- WriteModel: eventstore.WriteModel{
- AggregateID: domain.IAMID,
- ResourceOwner: domain.IAMID,
- },
- MailTextType: mailTextType,
- Language: language,
- },
- }
-}
-
-func (wm *IAMMailTextWriteModel) AppendEvents(events ...eventstore.EventReader) {
- for _, event := range events {
- switch e := event.(type) {
- case *iam.MailTextAddedEvent:
- wm.MailTextWriteModel.AppendEvents(&e.MailTextAddedEvent)
- case *iam.MailTextChangedEvent:
- wm.MailTextWriteModel.AppendEvents(&e.MailTextChangedEvent)
- }
- }
-}
-
-func (wm *IAMMailTextWriteModel) Reduce() error {
- return wm.MailTextWriteModel.Reduce()
-}
-
-func (wm *IAMMailTextWriteModel) Query() *eventstore.SearchQueryBuilder {
- return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, iam.AggregateType).
- AggregateIDs(wm.MailTextWriteModel.AggregateID).
- ResourceOwner(wm.ResourceOwner).
- EventTypes(
- iam.MailTextAddedEventType,
- iam.MailTextChangedEventType)
-}
-
-func (wm *IAMMailTextWriteModel) NewChangedEvent(
- ctx context.Context,
- aggregate *eventstore.Aggregate,
- mailTextType,
- language,
- title,
- preHeader,
- subject,
- greeting,
- text,
- buttonText string,
-) (*iam.MailTextChangedEvent, bool) {
- changes := make([]policy.MailTextChanges, 0)
- if wm.Title != title {
- changes = append(changes, policy.ChangeTitle(title))
- }
- if wm.PreHeader != preHeader {
- changes = append(changes, policy.ChangePreHeader(preHeader))
- }
- if wm.Subject != subject {
- changes = append(changes, policy.ChangeSubject(subject))
- }
- if wm.Greeting != greeting {
- changes = append(changes, policy.ChangeGreeting(greeting))
- }
- if wm.Text != text {
- changes = append(changes, policy.ChangeText(text))
- }
- if wm.ButtonText != buttonText {
- changes = append(changes, policy.ChangeButtonText(buttonText))
- }
- if len(changes) == 0 {
- return nil, false
- }
- changedEvent, err := iam.NewMailTextChangedEvent(ctx, aggregate, mailTextType, language, changes)
- if err != nil {
- return nil, false
- }
- return changedEvent, true
-}
diff --git a/internal/command/iam_policy_mail_text_test.go b/internal/command/iam_policy_mail_text_test.go
deleted file mode 100644
index d63a1057f6..0000000000
--- a/internal/command/iam_policy_mail_text_test.go
+++ /dev/null
@@ -1,366 +0,0 @@
-package command
-
-import (
- "context"
- "github.com/caos/zitadel/internal/domain"
- caos_errs "github.com/caos/zitadel/internal/errors"
- "github.com/caos/zitadel/internal/eventstore"
- "github.com/caos/zitadel/internal/eventstore/repository"
- "github.com/caos/zitadel/internal/eventstore/v1/models"
- "github.com/caos/zitadel/internal/repository/iam"
- "github.com/caos/zitadel/internal/repository/policy"
- "github.com/stretchr/testify/assert"
- "testing"
-)
-
-func TestCommandSide_AddDefaultMailTextPolicy(t *testing.T) {
- type fields struct {
- eventstore *eventstore.Eventstore
- }
- type args struct {
- ctx context.Context
- policy *domain.MailText
- }
- type res struct {
- want *domain.MailText
- err func(error) bool
- }
- tests := []struct {
- name string
- fields fields
- args args
- res res
- }{
- {
- name: "mail text invalid, invalid argument error",
- fields: fields{
- eventstore: eventstoreExpect(
- t,
- ),
- },
- args: args{
- ctx: context.Background(),
- policy: &domain.MailText{},
- },
- res: res{
- err: caos_errs.IsErrorInvalidArgument,
- },
- },
- {
- name: "mail text already existing, already exists error",
- fields: fields{
- eventstore: eventstoreExpect(
- t,
- expectFilter(
- eventFromEventPusher(
- iam.NewMailTextAddedEvent(context.Background(),
- &iam.NewAggregate().Aggregate,
- "mail-text-type",
- "de",
- "title",
- "pre-header",
- "subject",
- "greeting",
- "text",
- "button-text",
- ),
- ),
- ),
- ),
- },
- args: args{
- ctx: context.Background(),
- policy: &domain.MailText{
- MailTextType: "mail-text-type",
- Language: "de",
- Title: "title",
- PreHeader: "pre-header",
- Subject: "subject",
- Greeting: "greeting",
- Text: "text",
- ButtonText: "button-text",
- },
- },
- res: res{
- err: caos_errs.IsErrorAlreadyExists,
- },
- },
- {
- name: "add mail template,ok",
- fields: fields{
- eventstore: eventstoreExpect(
- t,
- expectFilter(),
- expectPush(
- []*repository.Event{
- eventFromEventPusher(
- iam.NewMailTextAddedEvent(context.Background(),
- &iam.NewAggregate().Aggregate,
- "mail-text-type",
- "de",
- "title",
- "pre-header",
- "subject",
- "greeting",
- "text",
- "button-text",
- ),
- ),
- },
- uniqueConstraintsFromEventConstraint(policy.NewAddMailTextUniqueConstraint("IAM", "mail-text-type", "de")),
- ),
- ),
- },
- args: args{
- ctx: context.Background(),
- policy: &domain.MailText{
- MailTextType: "mail-text-type",
- Language: "de",
- Title: "title",
- PreHeader: "pre-header",
- Subject: "subject",
- Greeting: "greeting",
- Text: "text",
- ButtonText: "button-text",
- },
- },
- res: res{
- want: &domain.MailText{
- ObjectRoot: models.ObjectRoot{
- AggregateID: "IAM",
- ResourceOwner: "IAM",
- },
- MailTextType: "mail-text-type",
- Language: "de",
- Title: "title",
- PreHeader: "pre-header",
- Subject: "subject",
- Greeting: "greeting",
- Text: "text",
- ButtonText: "button-text",
- State: domain.PolicyStateActive,
- },
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- r := &Commands{
- eventstore: tt.fields.eventstore,
- }
- got, err := r.AddDefaultMailText(tt.args.ctx, tt.args.policy)
- if tt.res.err == nil {
- assert.NoError(t, err)
- }
- if tt.res.err != nil && !tt.res.err(err) {
- t.Errorf("got wrong err: %v ", err)
- }
- if tt.res.err == nil {
- assert.Equal(t, tt.res.want, got)
- }
- })
- }
-}
-
-func TestCommandSide_ChangeDefaultMailTextPolicy(t *testing.T) {
- type fields struct {
- eventstore *eventstore.Eventstore
- }
- type args struct {
- ctx context.Context
- policy *domain.MailText
- }
- type res struct {
- want *domain.MailText
- err func(error) bool
- }
- tests := []struct {
- name string
- fields fields
- args args
- res res
- }{
- {
- name: "mailtext invalid, invalid argument error",
- fields: fields{
- eventstore: eventstoreExpect(
- t,
- ),
- },
- args: args{
- ctx: context.Background(),
- policy: &domain.MailText{},
- },
- res: res{
- err: caos_errs.IsErrorInvalidArgument,
- },
- },
- {
- name: "mail text not existing, not found error",
- fields: fields{
- eventstore: eventstoreExpect(
- t,
- expectFilter(),
- ),
- },
- args: args{
- ctx: context.Background(),
- policy: &domain.MailText{
- MailTextType: "mail-text-type",
- Language: "de",
- Title: "title",
- PreHeader: "pre-header",
- Subject: "subject",
- Greeting: "greeting",
- Text: "text",
- ButtonText: "button-text",
- },
- },
- res: res{
- err: caos_errs.IsNotFound,
- },
- },
- {
- name: "no changes, precondition error",
- fields: fields{
- eventstore: eventstoreExpect(
- t,
- expectFilter(
- eventFromEventPusher(
- iam.NewMailTextAddedEvent(context.Background(),
- &iam.NewAggregate().Aggregate,
- "mail-text-type",
- "de",
- "title",
- "pre-header",
- "subject",
- "greeting",
- "text",
- "button-text",
- ),
- ),
- ),
- ),
- },
- args: args{
- ctx: context.Background(),
- policy: &domain.MailText{
- MailTextType: "mail-text-type",
- Language: "de",
- Title: "title",
- PreHeader: "pre-header",
- Subject: "subject",
- Greeting: "greeting",
- Text: "text",
- ButtonText: "button-text",
- },
- },
- res: res{
- err: caos_errs.IsPreconditionFailed,
- },
- },
- {
- name: "change, ok",
- fields: fields{
- eventstore: eventstoreExpect(
- t,
- expectFilter(
- eventFromEventPusher(
- iam.NewMailTextAddedEvent(context.Background(),
- &iam.NewAggregate().Aggregate,
- "mail-text-type",
- "de",
- "title",
- "pre-header",
- "subject",
- "greeting",
- "text",
- "button-text",
- ),
- ),
- ),
- expectPush(
- []*repository.Event{
- eventFromEventPusher(
- newDefaultMailTextPolicyChangedEvent(
- context.Background(),
- "mail-text-type",
- "de",
- "title-change",
- "pre-header-change",
- "subject-change",
- "greeting-change",
- "text-change",
- "button-text-change"),
- ),
- },
- ),
- ),
- },
- args: args{
- ctx: context.Background(),
- policy: &domain.MailText{
- MailTextType: "mail-text-type",
- Language: "de",
- Title: "title-change",
- PreHeader: "pre-header-change",
- Subject: "subject-change",
- Greeting: "greeting-change",
- Text: "text-change",
- ButtonText: "button-text-change",
- },
- },
- res: res{
- want: &domain.MailText{
- ObjectRoot: models.ObjectRoot{
- AggregateID: "IAM",
- ResourceOwner: "IAM",
- },
- MailTextType: "mail-text-type",
- Language: "de",
- Title: "title-change",
- PreHeader: "pre-header-change",
- Subject: "subject-change",
- Greeting: "greeting-change",
- Text: "text-change",
- ButtonText: "button-text-change",
- State: domain.PolicyStateActive,
- },
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- r := &Commands{
- eventstore: tt.fields.eventstore,
- }
- got, err := r.ChangeDefaultMailText(tt.args.ctx, tt.args.policy)
- if tt.res.err == nil {
- assert.NoError(t, err)
- }
- if tt.res.err != nil && !tt.res.err(err) {
- t.Errorf("got wrong err: %v ", err)
- }
- if tt.res.err == nil {
- assert.Equal(t, tt.res.want, got)
- }
- })
- }
-}
-
-func newDefaultMailTextPolicyChangedEvent(ctx context.Context, mailTextType, language, title, preHeader, subject, greeting, text, buttonText string) *iam.MailTextChangedEvent {
- event, _ := iam.NewMailTextChangedEvent(ctx,
- &iam.NewAggregate().Aggregate,
- mailTextType,
- language,
- []policy.MailTextChanges{
- policy.ChangeTitle(title),
- policy.ChangePreHeader(preHeader),
- policy.ChangeSubject(subject),
- policy.ChangeGreeting(greeting),
- policy.ChangeText(text),
- policy.ChangeButtonText(buttonText),
- },
- )
- return event
-}
diff --git a/internal/command/org_custom_message_model.go b/internal/command/org_custom_message_model.go
new file mode 100644
index 0000000000..2001c0a1a2
--- /dev/null
+++ b/internal/command/org_custom_message_model.go
@@ -0,0 +1,95 @@
+package command
+
+import (
+ "golang.org/x/text/language"
+
+ "github.com/caos/zitadel/internal/eventstore"
+ "github.com/caos/zitadel/internal/repository/org"
+)
+
+type OrgCustomMessageTextReadModel struct {
+ CustomMessageTextReadModel
+}
+
+func NewOrgCustomMessageTextWriteModel(orgID, messageTextType string, lang language.Tag) *OrgCustomMessageTextReadModel {
+ return &OrgCustomMessageTextReadModel{
+ CustomMessageTextReadModel{
+ WriteModel: eventstore.WriteModel{
+ AggregateID: orgID,
+ ResourceOwner: orgID,
+ },
+ MessageTextType: messageTextType,
+ Language: lang,
+ },
+ }
+}
+
+func (wm *OrgCustomMessageTextReadModel) AppendEvents(events ...eventstore.EventReader) {
+ for _, event := range events {
+ switch e := event.(type) {
+ case *org.CustomTextSetEvent:
+ wm.CustomMessageTextReadModel.AppendEvents(&e.CustomTextSetEvent)
+ case *org.CustomTextRemovedEvent:
+ wm.CustomMessageTextReadModel.AppendEvents(&e.CustomTextRemovedEvent)
+ case *org.CustomTextTemplateRemovedEvent:
+ wm.CustomMessageTextReadModel.AppendEvents(&e.CustomTextTemplateRemovedEvent)
+ }
+ }
+}
+
+func (wm *OrgCustomMessageTextReadModel) Reduce() error {
+ return wm.CustomMessageTextReadModel.Reduce()
+}
+
+func (wm *OrgCustomMessageTextReadModel) Query() *eventstore.SearchQueryBuilder {
+ return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, org.AggregateType).
+ AggregateIDs(wm.CustomMessageTextReadModel.AggregateID).
+ ResourceOwner(wm.ResourceOwner).
+ EventTypes(
+ org.CustomTextSetEventType,
+ org.CustomTextRemovedEventType,
+ org.CustomTextTemplateRemovedEventType)
+}
+
+type OrgCustomMessageTemplatesReadModel struct {
+ CustomMessageTemplatesReadModel
+}
+
+func NewOrgCustomMessageTextsWriteModel(orgID string) *OrgCustomMessageTemplatesReadModel {
+ return &OrgCustomMessageTemplatesReadModel{
+ CustomMessageTemplatesReadModel{
+ WriteModel: eventstore.WriteModel{
+ AggregateID: orgID,
+ ResourceOwner: orgID,
+ },
+ CustomMessageTemplate: make(map[string]*CustomText),
+ },
+ }
+}
+
+func (wm *OrgCustomMessageTemplatesReadModel) AppendEvents(events ...eventstore.EventReader) {
+ for _, event := range events {
+ switch e := event.(type) {
+ case *org.CustomTextSetEvent:
+ wm.CustomMessageTemplatesReadModel.AppendEvents(&e.CustomTextSetEvent)
+ case *org.CustomTextRemovedEvent:
+ wm.CustomMessageTemplatesReadModel.AppendEvents(&e.CustomTextRemovedEvent)
+ case *org.CustomTextTemplateRemovedEvent:
+ wm.CustomMessageTemplatesReadModel.AppendEvents(&e.CustomTextTemplateRemovedEvent)
+ }
+ }
+}
+
+func (wm *OrgCustomMessageTemplatesReadModel) Reduce() error {
+ return wm.CustomMessageTemplatesReadModel.Reduce()
+}
+
+func (wm *OrgCustomMessageTemplatesReadModel) Query() *eventstore.SearchQueryBuilder {
+ return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, org.AggregateType).
+ AggregateIDs(wm.CustomMessageTemplatesReadModel.AggregateID).
+ ResourceOwner(wm.ResourceOwner).
+ EventTypes(
+ org.CustomTextSetEventType,
+ org.CustomTextRemovedEventType,
+ org.CustomTextTemplateRemovedEventType)
+}
diff --git a/internal/command/org_custom_message_text.go b/internal/command/org_custom_message_text.go
new file mode 100644
index 0000000000..52a40b4326
--- /dev/null
+++ b/internal/command/org_custom_message_text.go
@@ -0,0 +1,137 @@
+package command
+
+import (
+ "context"
+
+ "golang.org/x/text/language"
+
+ "github.com/caos/zitadel/internal/domain"
+ caos_errs "github.com/caos/zitadel/internal/errors"
+ "github.com/caos/zitadel/internal/eventstore"
+ "github.com/caos/zitadel/internal/repository/org"
+)
+
+func (c *Commands) SetOrgMessageText(ctx context.Context, resourceOwner string, messageText *domain.CustomMessageText) (*domain.ObjectDetails, error) {
+ if resourceOwner == "" {
+ return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-2biiR", "Errors.ResourceOwnerMissing")
+ }
+ orgAgg := org.NewAggregate(resourceOwner, resourceOwner)
+ events, existingMessageText, err := c.setOrgMessageText(ctx, &orgAgg.Aggregate, messageText)
+ if err != nil {
+ return nil, err
+ }
+ pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
+ if err != nil {
+ return nil, err
+ }
+ err = AppendAndReduce(existingMessageText, pushedEvents...)
+ if err != nil {
+ return nil, err
+ }
+ return writeModelToObjectDetails(&existingMessageText.WriteModel), nil
+}
+
+func (c *Commands) setOrgMessageText(ctx context.Context, orgAgg *eventstore.Aggregate, message *domain.CustomMessageText) ([]eventstore.EventPusher, *OrgCustomMessageTextReadModel, error) {
+ if !message.IsValid() {
+ return nil, nil, caos_errs.ThrowInvalidArgument(nil, "ORG-2jfsf", "Errors.CustomText.Invalid")
+ }
+
+ existingMessageText, err := c.orgCustomMessageTextWriteModelByID(ctx, orgAgg.ID, message.MessageTextType, message.Language)
+ if err != nil {
+ return nil, nil, err
+ }
+ events := make([]eventstore.EventPusher, 0)
+ if existingMessageText.Greeting != message.Greeting {
+ if message.Greeting != "" {
+ events = append(events, org.NewCustomTextSetEvent(ctx, orgAgg, message.MessageTextType, domain.MessageGreeting, message.Greeting, message.Language))
+ } else {
+ events = append(events, org.NewCustomTextRemovedEvent(ctx, orgAgg, message.MessageTextType, domain.MessageGreeting, message.Language))
+ }
+ }
+ if existingMessageText.Subject != message.Subject {
+ if message.Subject != "" {
+ events = append(events, org.NewCustomTextSetEvent(ctx, orgAgg, message.MessageTextType, domain.MessageSubject, message.Subject, message.Language))
+ } else {
+ events = append(events, org.NewCustomTextRemovedEvent(ctx, orgAgg, message.MessageTextType, domain.MessageSubject, message.Language))
+ }
+ }
+ if existingMessageText.Title != message.Title {
+ if message.Title != "" {
+ events = append(events, org.NewCustomTextSetEvent(ctx, orgAgg, message.MessageTextType, domain.MessageTitle, message.Title, message.Language))
+ } else {
+ events = append(events, org.NewCustomTextRemovedEvent(ctx, orgAgg, message.MessageTextType, domain.MessageTitle, message.Language))
+ }
+ }
+ if existingMessageText.PreHeader != message.PreHeader {
+ if message.PreHeader != "" {
+ events = append(events, org.NewCustomTextSetEvent(ctx, orgAgg, message.MessageTextType, domain.MessagePreHeader, message.PreHeader, message.Language))
+ } else {
+ events = append(events, org.NewCustomTextRemovedEvent(ctx, orgAgg, message.MessageTextType, domain.MessagePreHeader, message.Language))
+ }
+ }
+ if existingMessageText.Text != message.Text {
+ if message.Text != "" {
+ events = append(events, org.NewCustomTextSetEvent(ctx, orgAgg, message.MessageTextType, domain.MessageText, message.Text, message.Language))
+ } else {
+ events = append(events, org.NewCustomTextRemovedEvent(ctx, orgAgg, message.MessageTextType, domain.MessageText, message.Language))
+ }
+ }
+ if existingMessageText.ButtonText != message.ButtonText {
+ if message.ButtonText != "" {
+ events = append(events, org.NewCustomTextSetEvent(ctx, orgAgg, message.MessageTextType, domain.MessageButtonText, message.ButtonText, message.Language))
+ } else {
+ events = append(events, org.NewCustomTextRemovedEvent(ctx, orgAgg, message.MessageTextType, domain.MessageButtonText, message.Language))
+ }
+ }
+ if existingMessageText.FooterText != message.FooterText {
+ if message.FooterText != "" {
+ events = append(events, org.NewCustomTextSetEvent(ctx, orgAgg, message.MessageTextType, domain.MessageFooterText, message.FooterText, message.Language))
+ } else {
+ events = append(events, org.NewCustomTextRemovedEvent(ctx, orgAgg, message.MessageTextType, domain.MessageFooterText, message.Language))
+ }
+ }
+ return events, existingMessageText, nil
+}
+
+func (c *Commands) RemoveOrgMessageTexts(ctx context.Context, resourceOwner, messageTextType string, lang language.Tag) error {
+ if resourceOwner == "" {
+ return caos_errs.ThrowInvalidArgument(nil, "Org-3mfsf", "Errors.ResourceOwnerMissing")
+ }
+ if messageTextType == "" || lang == language.Und {
+ return caos_errs.ThrowInvalidArgument(nil, "Org-j59f", "Errors.CustomMessageText.Invalid")
+ }
+ customText, err := c.orgCustomMessageTextWriteModelByID(ctx, resourceOwner, messageTextType, lang)
+ if err != nil {
+ return err
+ }
+ if customText.State == domain.PolicyStateUnspecified || customText.State == domain.PolicyStateRemoved {
+ return caos_errs.ThrowNotFound(nil, "Org-3b8Jf", "Errors.CustomMessageText.NotFound")
+ }
+ orgAgg := OrgAggregateFromWriteModel(&customText.WriteModel)
+ _, err = c.eventstore.PushEvents(ctx, org.NewCustomTextTemplateRemovedEvent(ctx, orgAgg, messageTextType, lang))
+ return err
+}
+
+func (c *Commands) removeOrgMessageTextsIfExists(ctx context.Context, orgID string) ([]eventstore.EventPusher, error) {
+ msgTemplates := NewOrgCustomMessageTextsWriteModel(orgID)
+ err := c.eventstore.FilterToQueryReducer(ctx, msgTemplates)
+ if err != nil {
+ return nil, err
+ }
+
+ orgAgg := OrgAggregateFromWriteModel(&msgTemplates.WriteModel)
+ events := make([]eventstore.EventPusher, 0, len(msgTemplates.CustomMessageTemplate))
+ for _, tmpl := range msgTemplates.CustomMessageTemplate {
+ events = append(events, org.NewCustomTextTemplateRemovedEvent(ctx, orgAgg, tmpl.Template, tmpl.Language))
+ }
+ return events, nil
+}
+
+func (c *Commands) orgCustomMessageTextWriteModelByID(ctx context.Context, orgID, messageType string, lang language.Tag) (*OrgCustomMessageTextReadModel, error) {
+ writeModel := NewOrgCustomMessageTextWriteModel(orgID, messageType, lang)
+ err := c.eventstore.FilterToQueryReducer(ctx, writeModel)
+ if err != nil {
+ return nil, err
+ }
+ return writeModel, nil
+}
diff --git a/internal/command/org_custom_message_text_test.go b/internal/command/org_custom_message_text_test.go
new file mode 100644
index 0000000000..2f52f4f36e
--- /dev/null
+++ b/internal/command/org_custom_message_text_test.go
@@ -0,0 +1,514 @@
+package command
+
+import (
+ "context"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "golang.org/x/text/language"
+
+ "github.com/caos/zitadel/internal/domain"
+ caos_errs "github.com/caos/zitadel/internal/errors"
+ "github.com/caos/zitadel/internal/eventstore"
+ "github.com/caos/zitadel/internal/eventstore/repository"
+ "github.com/caos/zitadel/internal/repository/org"
+)
+
+func TestCommandSide_SetCustomMessageText(t *testing.T) {
+ type fields struct {
+ eventstore *eventstore.Eventstore
+ }
+ type args struct {
+ ctx context.Context
+ resourceOwner string
+ config *domain.CustomMessageText
+ }
+ type res struct {
+ want *domain.ObjectDetails
+ err func(error) bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ res res
+ }{
+ {
+ name: "no resource owner, error",
+ fields: fields{
+ eventstore: eventstoreExpect(
+ t,
+ ),
+ },
+ args: args{
+ ctx: context.Background(),
+ config: &domain.CustomMessageText{},
+ },
+ res: res{
+ err: caos_errs.IsErrorInvalidArgument,
+ },
+ },
+ {
+ name: "invalid custom text, error",
+ fields: fields{
+ eventstore: eventstoreExpect(
+ t,
+ ),
+ },
+ args: args{
+ ctx: context.Background(),
+ resourceOwner: "org1",
+ config: &domain.CustomMessageText{},
+ },
+ res: res{
+ err: caos_errs.IsErrorInvalidArgument,
+ },
+ },
+ {
+ name: "custom text set all fields, ok",
+ fields: fields{
+ eventstore: eventstoreExpect(
+ t,
+ expectFilter(),
+ expectPush(
+ []*repository.Event{
+ eventFromEventPusher(
+ org.NewCustomTextSetEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageGreeting,
+ "Greeting",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextSetEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageSubject,
+ "Subject",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextSetEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageTitle,
+ "Title",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextSetEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessagePreHeader,
+ "PreHeader",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextSetEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageText,
+ "Text",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextSetEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageButtonText,
+ "ButtonText",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextSetEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageFooterText,
+ "FooterText",
+ language.English,
+ ),
+ ),
+ },
+ ),
+ ),
+ },
+ args: args{
+ ctx: context.Background(),
+ resourceOwner: "org1",
+ config: &domain.CustomMessageText{
+ MessageTextType: "Template",
+ Language: language.English,
+ Greeting: "Greeting",
+ Subject: "Subject",
+ Title: "Title",
+ PreHeader: "PreHeader",
+ Text: "Text",
+ ButtonText: "ButtonText",
+ FooterText: "FooterText",
+ },
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ },
+ },
+ },
+ {
+ name: "custom text remove all fields, ok",
+ fields: fields{
+ eventstore: eventstoreExpect(
+ t,
+ expectFilter(
+ eventFromEventPusher(
+ org.NewCustomTextSetEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageGreeting,
+ "Greeting",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextSetEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageSubject,
+ "Subject",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextSetEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageTitle,
+ "Title",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextSetEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessagePreHeader,
+ "PreHeader",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextSetEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageText,
+ "Text",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextSetEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageButtonText,
+ "ButtonText",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextSetEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageFooterText,
+ "FooterText",
+ language.English,
+ ),
+ ),
+ ),
+ expectPush(
+ []*repository.Event{
+ eventFromEventPusher(
+ org.NewCustomTextRemovedEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageGreeting,
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextRemovedEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageSubject,
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextRemovedEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageTitle,
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextRemovedEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessagePreHeader,
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextRemovedEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageText,
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextRemovedEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageButtonText,
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextRemovedEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageFooterText,
+ language.English,
+ ),
+ ),
+ },
+ ),
+ ),
+ },
+ args: args{
+ ctx: context.Background(),
+ resourceOwner: "org1",
+ config: &domain.CustomMessageText{
+ MessageTextType: "Template",
+ Language: language.English,
+ Greeting: "",
+ Subject: "",
+ Title: "",
+ PreHeader: "",
+ Text: "",
+ ButtonText: "",
+ FooterText: "",
+ },
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ r := &Commands{
+ eventstore: tt.fields.eventstore,
+ }
+ got, err := r.SetOrgMessageText(tt.args.ctx, tt.args.resourceOwner, tt.args.config)
+ if tt.res.err == nil {
+ assert.NoError(t, err)
+ }
+ if tt.res.err != nil && !tt.res.err(err) {
+ t.Errorf("got wrong err: %v ", err)
+ }
+ if tt.res.err == nil {
+ assert.Equal(t, tt.res.want, got)
+ }
+ })
+ }
+}
+
+func TestCommandSide_RemoveCustomMessageText(t *testing.T) {
+ type fields struct {
+ eventstore *eventstore.Eventstore
+ }
+ type args struct {
+ ctx context.Context
+ resourceOwner string
+ mailTextType string
+ lang language.Tag
+ }
+ type res struct {
+ want *domain.ObjectDetails
+ err func(error) bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ res res
+ }{
+ {
+ name: "no resource owner, error",
+ fields: fields{
+ eventstore: eventstoreExpect(
+ t,
+ ),
+ },
+ args: args{
+ ctx: context.Background(),
+ mailTextType: "Template",
+ lang: language.English,
+ },
+ res: res{
+ err: caos_errs.IsErrorInvalidArgument,
+ },
+ },
+ {
+ name: "no mail text type owner, error",
+ fields: fields{
+ eventstore: eventstoreExpect(
+ t,
+ ),
+ },
+ args: args{
+ ctx: context.Background(),
+ resourceOwner: "org1",
+ lang: language.English,
+ },
+ res: res{
+ err: caos_errs.IsErrorInvalidArgument,
+ },
+ },
+ {
+ name: "no mail text type owner, error",
+ fields: fields{
+ eventstore: eventstoreExpect(
+ t,
+ ),
+ },
+ args: args{
+ ctx: context.Background(),
+ resourceOwner: "org1",
+ mailTextType: "Template",
+ },
+ res: res{
+ err: caos_errs.IsErrorInvalidArgument,
+ },
+ },
+ {
+ name: "custom text remove all fields, ok",
+ fields: fields{
+ eventstore: eventstoreExpect(
+ t,
+ expectFilter(
+ eventFromEventPusher(
+ org.NewCustomTextSetEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageGreeting,
+ "Greeting",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextSetEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageSubject,
+ "Subject",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextSetEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageTitle,
+ "Title",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextSetEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessagePreHeader,
+ "PreHeader",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextSetEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageText,
+ "Text",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextSetEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageButtonText,
+ "ButtonText",
+ language.English,
+ ),
+ ),
+ eventFromEventPusher(
+ org.NewCustomTextSetEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ domain.MessageFooterText,
+ "FooterText",
+ language.English,
+ ),
+ ),
+ ),
+ expectPush(
+ []*repository.Event{
+ eventFromEventPusher(
+ org.NewCustomTextTemplateRemovedEvent(context.Background(),
+ &org.NewAggregate("org1", "org1").Aggregate,
+ "Template",
+ language.English,
+ ),
+ ),
+ },
+ ),
+ ),
+ },
+ args: args{
+ ctx: context.Background(),
+ resourceOwner: "org1",
+ mailTextType: "Template",
+ lang: language.English,
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "org1",
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ r := &Commands{
+ eventstore: tt.fields.eventstore,
+ }
+ err := r.RemoveOrgMessageTexts(tt.args.ctx, tt.args.resourceOwner, tt.args.mailTextType, tt.args.lang)
+ if tt.res.err == nil {
+ assert.NoError(t, err)
+ }
+ if tt.res.err != nil && !tt.res.err(err) {
+ t.Errorf("got wrong err: %v ", err)
+ }
+ })
+ }
+}
diff --git a/internal/command/org_features.go b/internal/command/org_features.go
index 47f28c6179..85be241c1f 100644
--- a/internal/command/org_features.go
+++ b/internal/command/org_features.go
@@ -40,6 +40,7 @@ func (c *Commands) SetOrgFeatures(ctx context.Context, resourceOwner string, fea
features.LabelPolicyPrivateLabel,
features.LabelPolicyWatermark,
features.CustomDomain,
+ features.CustomText,
)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Features-GE4h2", "Errors.Features.NotChanged")
@@ -126,6 +127,15 @@ func (c *Commands) ensureOrgSettingsToFeatures(ctx context.Context, orgID string
events = append(events, removeCustomDomainsEvents...)
}
}
+ if !features.CustomText {
+ removeCustomTextEvents, err := c.removeOrgMessageTextsIfExists(ctx, orgID)
+ if err != nil {
+ return nil, err
+ }
+ if removeCustomTextEvents != nil {
+ events = append(events, removeCustomTextEvents...)
+ }
+ }
return events, nil
}
diff --git a/internal/command/org_features_model.go b/internal/command/org_features_model.go
index 956c021f52..7855fa8695 100644
--- a/internal/command/org_features_model.go
+++ b/internal/command/org_features_model.go
@@ -71,7 +71,8 @@ func (wm *OrgFeaturesWriteModel) NewSetEvent(
passwordComplexityPolicy,
labelPolicyPrivateLabel,
labelPolicyWatermark,
- customDomain bool,
+ customDomain,
+ customText bool,
) (*org.FeaturesSetEvent, bool) {
changes := make([]features.FeaturesChanges, 0)
@@ -121,6 +122,9 @@ func (wm *OrgFeaturesWriteModel) NewSetEvent(
if wm.CustomDomain != customDomain {
changes = append(changes, features.ChangeCustomDomain(customDomain))
}
+ if wm.CustomText != customText {
+ changes = append(changes, features.ChangeCustomText(customText))
+ }
if len(changes) == 0 {
return nil, false
diff --git a/internal/command/org_features_test.go b/internal/command/org_features_test.go
index 5c43a6a2e2..f902c1f438 100644
--- a/internal/command/org_features_test.go
+++ b/internal/command/org_features_test.go
@@ -7,6 +7,7 @@ import (
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
+ "golang.org/x/text/language"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
@@ -226,6 +227,18 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
),
+ expectFilter(
+ eventFromEventPusher(
+ iam.NewCustomTextSetEvent(
+ context.Background(),
+ &iam.NewAggregate().Aggregate,
+ domain.InitCodeMessageType,
+ domain.MessageSubject,
+ "text",
+ language.English,
+ ),
+ ),
+ ),
expectPush(
[]*repository.Event{
eventFromEventPusher(
@@ -253,6 +266,7 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
LabelPolicyPrivateLabel: false,
LabelPolicyWatermark: false,
CustomDomain: false,
+ CustomText: false,
},
},
res: res{
@@ -373,6 +387,18 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
),
+ expectFilter(
+ eventFromEventPusher(
+ iam.NewCustomTextSetEvent(
+ context.Background(),
+ &iam.NewAggregate().Aggregate,
+ domain.InitCodeMessageType,
+ domain.MessageSubject,
+ "text",
+ language.English,
+ ),
+ ),
+ ),
expectPush(
[]*repository.Event{
eventFromEventPusher(
@@ -534,6 +560,18 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
),
+ expectFilter(
+ eventFromEventPusher(
+ iam.NewCustomTextSetEvent(
+ context.Background(),
+ &iam.NewAggregate().Aggregate,
+ domain.InitCodeMessageType,
+ domain.MessageSubject,
+ "text",
+ language.English,
+ ),
+ ),
+ ),
expectPush(
[]*repository.Event{
eventFromEventPusher(
@@ -705,6 +743,18 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
),
+ expectFilter(
+ eventFromEventPusher(
+ iam.NewCustomTextSetEvent(
+ context.Background(),
+ &iam.NewAggregate().Aggregate,
+ domain.InitCodeMessageType,
+ domain.MessageSubject,
+ "text",
+ language.English,
+ ),
+ ),
+ ),
expectPush(
[]*repository.Event{
eventFromEventPusher(
@@ -931,6 +981,18 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
),
),
),
+ expectFilter(
+ eventFromEventPusher(
+ org.NewCustomTextSetEvent(
+ context.Background(),
+ &iam.NewAggregate().Aggregate,
+ domain.InitCodeMessageType,
+ domain.MessageSubject,
+ "text",
+ language.English,
+ ),
+ ),
+ ),
expectPush(
[]*repository.Event{
eventFromEventPusher(
@@ -951,6 +1013,9 @@ func TestCommandSide_SetOrgFeatures(t *testing.T) {
eventFromEventPusher(
org.NewLabelPolicyRemovedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate),
),
+ eventFromEventPusher(
+ org.NewCustomTextTemplateRemovedEvent(context.Background(), &org.NewAggregate("org1", "org1").Aggregate, domain.InitCodeMessageType, language.English),
+ ),
eventFromEventPusher(
newFeaturesSetEvent(context.Background(), "org1", "Test", domain.FeaturesStateActive, time.Hour),
),
@@ -1144,6 +1209,18 @@ func TestCommandSide_RemoveOrgFeatures(t *testing.T) {
),
),
),
+ expectFilter(
+ eventFromEventPusher(
+ iam.NewCustomTextSetEvent(
+ context.Background(),
+ &iam.NewAggregate().Aggregate,
+ domain.InitCodeMessageType,
+ domain.MessageSubject,
+ "text",
+ language.English,
+ ),
+ ),
+ ),
expectPush(
[]*repository.Event{
eventFromEventPusher(
diff --git a/internal/command/org_policy_mail_text.go b/internal/command/org_policy_mail_text.go
deleted file mode 100644
index c325c6091e..0000000000
--- a/internal/command/org_policy_mail_text.go
+++ /dev/null
@@ -1,114 +0,0 @@
-package command
-
-import (
- "context"
-
- "github.com/caos/zitadel/internal/domain"
- caos_errs "github.com/caos/zitadel/internal/errors"
- "github.com/caos/zitadel/internal/repository/org"
-)
-
-func (c *Commands) AddMailText(ctx context.Context, resourceOwner string, mailText *domain.MailText) (*domain.MailText, error) {
- if resourceOwner == "" {
- return nil, caos_errs.ThrowInvalidArgument(nil, "Org-MFiig", "Errors.ResourceOwnerMissing")
- }
- if !mailText.IsValid() {
- return nil, caos_errs.ThrowInvalidArgument(nil, "Org-4778u", "Errors.Org.MailText.Invalid")
- }
- addedPolicy := NewOrgMailTextWriteModel(resourceOwner, mailText.MailTextType, mailText.Language)
- err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
- if err != nil {
- return nil, err
- }
- if addedPolicy.State == domain.PolicyStateActive {
- return nil, caos_errs.ThrowAlreadyExists(nil, "Org-9kufs", "Errors.Org.MailText.AlreadyExists")
- }
-
- orgAgg := OrgAggregateFromWriteModel(&addedPolicy.MailTextWriteModel.WriteModel)
- pushedEvents, err := c.eventstore.PushEvents(
- ctx,
- org.NewMailTextAddedEvent(
- ctx,
- orgAgg,
- mailText.MailTextType,
- mailText.Language,
- mailText.Title,
- mailText.PreHeader,
- mailText.Subject,
- mailText.Greeting,
- mailText.Text,
- mailText.ButtonText))
- if err != nil {
- return nil, err
- }
- err = AppendAndReduce(addedPolicy, pushedEvents...)
- if err != nil {
- return nil, err
- }
-
- return writeModelToMailText(&addedPolicy.MailTextWriteModel), nil
-}
-
-func (c *Commands) ChangeMailText(ctx context.Context, resourceOwner string, mailText *domain.MailText) (*domain.MailText, error) {
- if resourceOwner == "" {
- return nil, caos_errs.ThrowInvalidArgument(nil, "Org-NFus3", "Errors.ResourceOwnerMissing")
- }
- if !mailText.IsValid() {
- return nil, caos_errs.ThrowInvalidArgument(nil, "Org-3m9fs", "Errors.Org.MailText.Invalid")
- }
- existingPolicy := NewOrgMailTextWriteModel(resourceOwner, mailText.MailTextType, mailText.Language)
- err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
- if err != nil {
- return nil, err
- }
- if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
- return nil, caos_errs.ThrowNotFound(nil, "Org-3n8fM", "Errors.Org.MailText.NotFound")
- }
-
- orgAgg := OrgAggregateFromWriteModel(&existingPolicy.MailTextWriteModel.WriteModel)
- changedEvent, hasChanged := existingPolicy.NewChangedEvent(
- ctx,
- orgAgg,
- mailText.MailTextType,
- mailText.Language,
- mailText.Title,
- mailText.PreHeader,
- mailText.Subject,
- mailText.Greeting,
- mailText.Text,
- mailText.ButtonText)
- if !hasChanged {
- return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-2n9fs", "Errors.Org.MailText.NotChanged")
- }
-
- pushedEvents, err := c.eventstore.PushEvents(ctx, changedEvent)
- if err != nil {
- return nil, err
- }
- err = AppendAndReduce(existingPolicy, pushedEvents...)
- if err != nil {
- return nil, err
- }
-
- return writeModelToMailText(&existingPolicy.MailTextWriteModel), nil
-}
-
-func (c *Commands) RemoveMailText(ctx context.Context, resourceOwner, mailTextType, language string) error {
- if resourceOwner == "" {
- return caos_errs.ThrowInvalidArgument(nil, "Org-2N7fd", "Errors.ResourceOwnerMissing")
- }
- if mailTextType == "" || language == "" {
- return caos_errs.ThrowInvalidArgument(nil, "Org-N8fsf", "Errors.Org.MailText.Invalid")
- }
- existingPolicy := NewOrgMailTextWriteModel(resourceOwner, mailTextType, language)
- err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
- if err != nil {
- return err
- }
- if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
- return caos_errs.ThrowNotFound(nil, "Org-3b8Jf", "Errors.Org.MailText.NotFound")
- }
- orgAgg := OrgAggregateFromWriteModel(&existingPolicy.WriteModel)
- _, err = c.eventstore.PushEvents(ctx, org.NewMailTextRemovedEvent(ctx, orgAgg, mailTextType, language))
- return err
-}
diff --git a/internal/command/org_policy_mail_text_model.go b/internal/command/org_policy_mail_text_model.go
deleted file mode 100644
index ae8a8cc9e5..0000000000
--- a/internal/command/org_policy_mail_text_model.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package command
-
-import (
- "context"
- "github.com/caos/zitadel/internal/eventstore"
- "github.com/caos/zitadel/internal/repository/org"
- "github.com/caos/zitadel/internal/repository/policy"
-)
-
-type OrgMailTextWriteModel struct {
- MailTextWriteModel
-}
-
-func NewOrgMailTextWriteModel(orgID, mailTextType, language string) *OrgMailTextWriteModel {
- return &OrgMailTextWriteModel{
- MailTextWriteModel{
- WriteModel: eventstore.WriteModel{
- AggregateID: orgID,
- ResourceOwner: orgID,
- },
- MailTextType: mailTextType,
- Language: language,
- },
- }
-}
-
-func (wm *OrgMailTextWriteModel) AppendEvents(events ...eventstore.EventReader) {
- for _, event := range events {
- switch e := event.(type) {
- case *org.MailTextAddedEvent:
- wm.MailTextWriteModel.AppendEvents(&e.MailTextAddedEvent)
- case *org.MailTextChangedEvent:
- wm.MailTextWriteModel.AppendEvents(&e.MailTextChangedEvent)
- case *org.MailTextRemovedEvent:
- wm.MailTextWriteModel.AppendEvents(&e.MailTextRemovedEvent)
- }
- }
-}
-
-func (wm *OrgMailTextWriteModel) Reduce() error {
- return wm.MailTextWriteModel.Reduce()
-}
-
-func (wm *OrgMailTextWriteModel) Query() *eventstore.SearchQueryBuilder {
- query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, org.AggregateType).
- AggregateIDs(wm.MailTextWriteModel.AggregateID).
- EventTypes(org.MailTextAddedEventType,
- org.MailTextChangedEventType,
- org.MailTextRemovedEventType)
- if wm.ResourceOwner != "" {
- query.ResourceOwner(wm.ResourceOwner)
- }
- return query
-}
-
-func (wm *OrgMailTextWriteModel) NewChangedEvent(
- ctx context.Context,
- aggregate *eventstore.Aggregate,
- mailTextType,
- language,
- title,
- preHeader,
- subject,
- greeting,
- text,
- buttonText string,
-) (*org.MailTextChangedEvent, bool) {
- changes := make([]policy.MailTextChanges, 0)
- if wm.Title != title {
- changes = append(changes, policy.ChangeTitle(title))
- }
- if wm.PreHeader != preHeader {
- changes = append(changes, policy.ChangePreHeader(preHeader))
- }
- if wm.Subject != subject {
- changes = append(changes, policy.ChangeSubject(subject))
- }
- if wm.Greeting != greeting {
- changes = append(changes, policy.ChangeGreeting(greeting))
- }
- if wm.Text != text {
- changes = append(changes, policy.ChangeText(text))
- }
- if wm.ButtonText != buttonText {
- changes = append(changes, policy.ChangeButtonText(buttonText))
- }
- if len(changes) == 0 {
- return nil, false
- }
- changedEvent, err := org.NewMailTextChangedEvent(ctx, aggregate, mailTextType, language, changes)
- if err != nil {
- return nil, false
- }
- return changedEvent, true
-}
diff --git a/internal/command/org_policy_mail_text_test.go b/internal/command/org_policy_mail_text_test.go
deleted file mode 100644
index b8ddfcbcc8..0000000000
--- a/internal/command/org_policy_mail_text_test.go
+++ /dev/null
@@ -1,563 +0,0 @@
-package command
-
-import (
- "context"
- "testing"
-
- "github.com/stretchr/testify/assert"
-
- "github.com/caos/zitadel/internal/domain"
- caos_errs "github.com/caos/zitadel/internal/errors"
- "github.com/caos/zitadel/internal/eventstore"
- "github.com/caos/zitadel/internal/eventstore/repository"
- "github.com/caos/zitadel/internal/eventstore/v1/models"
- "github.com/caos/zitadel/internal/repository/org"
- "github.com/caos/zitadel/internal/repository/policy"
-)
-
-func TestCommandSide_AddMailText(t *testing.T) {
- type fields struct {
- eventstore *eventstore.Eventstore
- }
- type args struct {
- ctx context.Context
- orgID string
- policy *domain.MailText
- }
- type res struct {
- want *domain.MailText
- err func(error) bool
- }
- tests := []struct {
- name string
- fields fields
- args args
- res res
- }{
- {
- name: "org id missing, invalid argument error",
- fields: fields{
- eventstore: eventstoreExpect(
- t,
- ),
- },
- args: args{
- ctx: context.Background(),
- policy: &domain.MailText{
- MailTextType: "mail-text-type",
- Language: "de",
- Title: "title",
- PreHeader: "pre-header",
- Subject: "subject",
- Greeting: "greeting",
- Text: "text",
- ButtonText: "button-text",
- },
- },
- res: res{
- err: caos_errs.IsErrorInvalidArgument,
- },
- },
- {
- name: "mail text already existing, already exists error",
- fields: fields{
- eventstore: eventstoreExpect(
- t,
- expectFilter(
- eventFromEventPusher(
- org.NewMailTextAddedEvent(context.Background(),
- &org.NewAggregate("org1", "org1").Aggregate,
- "mail-text-type",
- "de",
- "title",
- "pre-header",
- "subject",
- "greeting",
- "text",
- "button-text",
- ),
- ),
- ),
- ),
- },
- args: args{
- ctx: context.Background(),
- orgID: "org1",
- policy: &domain.MailText{
- MailTextType: "mail-text-type",
- Language: "de",
- Title: "title",
- PreHeader: "pre-header",
- Subject: "subject",
- Greeting: "greeting",
- Text: "text",
- ButtonText: "button-text",
- },
- },
- res: res{
- err: caos_errs.IsErrorAlreadyExists,
- },
- },
- {
- name: "mail text already existing, already exists error",
- fields: fields{
- eventstore: eventstoreExpect(
- t,
- expectFilter(
- eventFromEventPusher(
- org.NewMailTextAddedEvent(context.Background(),
- &org.NewAggregate("org1", "org1").Aggregate,
- "mail-text-type",
- "de",
- "title",
- "pre-header",
- "subject",
- "greeting",
- "text",
- "button-text",
- ),
- ),
- ),
- ),
- },
- args: args{
- ctx: context.Background(),
- orgID: "org1",
- policy: &domain.MailText{
- MailTextType: "mail-text-type",
- Language: "de",
- Title: "title",
- PreHeader: "pre-header",
- Subject: "subject",
- Greeting: "greeting",
- Text: "text",
- ButtonText: "button-text",
- },
- },
- res: res{
- err: caos_errs.IsErrorAlreadyExists,
- },
- },
- {
- name: "add policy,ok",
- fields: fields{
- eventstore: eventstoreExpect(
- t,
- expectFilter(),
- expectPush(
- []*repository.Event{
- eventFromEventPusher(
- org.NewMailTextAddedEvent(context.Background(),
- &org.NewAggregate("org1", "org1").Aggregate,
- "mail-text-type",
- "de",
- "title",
- "pre-header",
- "subject",
- "greeting",
- "text",
- "button-text",
- ),
- ),
- },
- uniqueConstraintsFromEventConstraint(policy.NewAddMailTextUniqueConstraint("org1", "mail-text-type", "de")),
- ),
- ),
- },
- args: args{
- ctx: context.Background(),
- orgID: "org1",
- policy: &domain.MailText{
- MailTextType: "mail-text-type",
- Language: "de",
- Title: "title",
- PreHeader: "pre-header",
- Subject: "subject",
- Greeting: "greeting",
- Text: "text",
- ButtonText: "button-text",
- },
- },
- res: res{
- want: &domain.MailText{
- ObjectRoot: models.ObjectRoot{
- AggregateID: "org1",
- ResourceOwner: "org1",
- },
- MailTextType: "mail-text-type",
- Language: "de",
- Title: "title",
- PreHeader: "pre-header",
- Subject: "subject",
- Greeting: "greeting",
- Text: "text",
- ButtonText: "button-text",
- State: domain.PolicyStateActive,
- },
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- r := &Commands{
- eventstore: tt.fields.eventstore,
- }
- got, err := r.AddMailText(tt.args.ctx, tt.args.orgID, tt.args.policy)
- if tt.res.err == nil {
- assert.NoError(t, err)
- }
- if tt.res.err != nil && !tt.res.err(err) {
- t.Errorf("got wrong err: %v ", err)
- }
- if tt.res.err == nil {
- assert.Equal(t, tt.res.want, got)
- }
- })
- }
-}
-
-func TestCommandSide_ChangeMailText(t *testing.T) {
- type fields struct {
- eventstore *eventstore.Eventstore
- }
- type args struct {
- ctx context.Context
- orgID string
- policy *domain.MailText
- }
- type res struct {
- want *domain.MailText
- err func(error) bool
- }
- tests := []struct {
- name string
- fields fields
- args args
- res res
- }{
- {
- name: "org id missing, invalid argument error",
- fields: fields{
- eventstore: eventstoreExpect(
- t,
- ),
- },
- args: args{
- ctx: context.Background(),
- policy: &domain.MailText{
- MailTextType: "mail-text-type",
- Language: "de",
- Title: "title",
- PreHeader: "pre-header",
- Subject: "subject",
- Greeting: "greeting",
- Text: "text",
- ButtonText: "button-text",
- },
- },
- res: res{
- err: caos_errs.IsErrorInvalidArgument,
- },
- },
- {
- name: "mailtext invalid, invalid argument error",
- fields: fields{
- eventstore: eventstoreExpect(
- t,
- ),
- },
- args: args{
- ctx: context.Background(),
- policy: &domain.MailText{},
- },
- res: res{
- err: caos_errs.IsErrorInvalidArgument,
- },
- },
- {
- name: "mail template not existing, not found error",
- fields: fields{
- eventstore: eventstoreExpect(
- t,
- expectFilter(),
- ),
- },
- args: args{
- ctx: context.Background(),
- orgID: "org1",
- policy: &domain.MailText{
- MailTextType: "mail-text-type",
- Language: "de",
- Title: "title",
- PreHeader: "pre-header",
- Subject: "subject",
- Greeting: "greeting",
- Text: "text",
- ButtonText: "button-text",
- },
- },
- res: res{
- err: caos_errs.IsNotFound,
- },
- },
- {
- name: "no changes, precondition error",
- fields: fields{
- eventstore: eventstoreExpect(
- t,
- expectFilter(
- eventFromEventPusher(
- org.NewMailTextAddedEvent(context.Background(),
- &org.NewAggregate("org1", "org1").Aggregate,
- "mail-text-type",
- "de",
- "title",
- "pre-header",
- "subject",
- "greeting",
- "text",
- "button-text",
- ),
- ),
- ),
- ),
- },
- args: args{
- ctx: context.Background(),
- orgID: "org1",
- policy: &domain.MailText{
- MailTextType: "mail-text-type",
- Language: "de",
- Title: "title",
- PreHeader: "pre-header",
- Subject: "subject",
- Greeting: "greeting",
- Text: "text",
- ButtonText: "button-text",
- },
- },
- res: res{
- err: caos_errs.IsPreconditionFailed,
- },
- },
- {
- name: "change, ok",
- fields: fields{
- eventstore: eventstoreExpect(
- t,
- expectFilter(
- eventFromEventPusher(
- org.NewMailTextAddedEvent(context.Background(),
- &org.NewAggregate("org1", "org1").Aggregate,
- "mail-text-type",
- "de",
- "title",
- "pre-header",
- "subject",
- "greeting",
- "text",
- "button-text",
- ),
- ),
- ),
- expectPush(
- []*repository.Event{
- eventFromEventPusher(
- newMailTextChangedEvent(
- context.Background(),
- "org1",
- "mail-text-type",
- "de",
- "title-change",
- "pre-header-change",
- "subject-change",
- "greeting-change",
- "text-change",
- "button-text-change"),
- ),
- },
- ),
- ),
- },
- args: args{
- ctx: context.Background(),
- orgID: "org1",
- policy: &domain.MailText{
- MailTextType: "mail-text-type",
- Language: "de",
- Title: "title-change",
- PreHeader: "pre-header-change",
- Subject: "subject-change",
- Greeting: "greeting-change",
- Text: "text-change",
- ButtonText: "button-text-change",
- },
- },
- res: res{
- want: &domain.MailText{
- ObjectRoot: models.ObjectRoot{
- AggregateID: "org1",
- ResourceOwner: "org1",
- },
- MailTextType: "mail-text-type",
- Language: "de",
- Title: "title-change",
- PreHeader: "pre-header-change",
- Subject: "subject-change",
- Greeting: "greeting-change",
- Text: "text-change",
- ButtonText: "button-text-change",
- State: domain.PolicyStateActive,
- },
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- r := &Commands{
- eventstore: tt.fields.eventstore,
- }
- got, err := r.ChangeMailText(tt.args.ctx, tt.args.orgID, tt.args.policy)
- if tt.res.err == nil {
- assert.NoError(t, err)
- }
- if tt.res.err != nil && !tt.res.err(err) {
- t.Errorf("got wrong err: %v ", err)
- }
- if tt.res.err == nil {
- assert.Equal(t, tt.res.want, got)
- }
- })
- }
-}
-
-func TestCommandSide_RemoveMailText(t *testing.T) {
- type fields struct {
- eventstore *eventstore.Eventstore
- }
- type args struct {
- ctx context.Context
- orgID string
- mailTextType string
- language string
- }
- type res struct {
- want *domain.ObjectDetails
- err func(error) bool
- }
- tests := []struct {
- name string
- fields fields
- args args
- res res
- }{
- {
- name: "org id missing, invalid argument error",
- fields: fields{
- eventstore: eventstoreExpect(
- t,
- ),
- },
- args: args{
- ctx: context.Background(),
- },
- res: res{
- err: caos_errs.IsErrorInvalidArgument,
- },
- },
- {
- name: "policy not existing, not found error",
- fields: fields{
- eventstore: eventstoreExpect(
- t,
- expectFilter(),
- ),
- },
- args: args{
- ctx: context.Background(),
- orgID: "org1",
- mailTextType: "mail-text-type",
- language: "de",
- },
- res: res{
- err: caos_errs.IsNotFound,
- },
- },
- {
- name: "remove, ok",
- fields: fields{
- eventstore: eventstoreExpect(
- t,
- expectFilter(
- eventFromEventPusher(
- org.NewMailTextAddedEvent(context.Background(),
- &org.NewAggregate("org1", "org1").Aggregate,
- "mail-text-type",
- "de",
- "title",
- "pre-header",
- "subject",
- "greeting",
- "text",
- "button-text",
- ),
- ),
- ),
- expectPush(
- []*repository.Event{
- eventFromEventPusher(
- org.NewMailTextRemovedEvent(context.Background(),
- &org.NewAggregate("org1", "org1").Aggregate,
- "mail-text-type",
- "de"),
- ),
- },
- uniqueConstraintsFromEventConstraint(policy.NewRemoveMailTextUniqueConstraint("org1", "mail-text-type", "de")),
- ),
- ),
- },
- args: args{
- ctx: context.Background(),
- orgID: "org1",
- mailTextType: "mail-text-type",
- language: "de",
- },
- res: res{
- want: &domain.ObjectDetails{
- ResourceOwner: "org1",
- },
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- r := &Commands{
- eventstore: tt.fields.eventstore,
- }
- err := r.RemoveMailText(tt.args.ctx, tt.args.orgID, tt.args.mailTextType, tt.args.language)
- if tt.res.err == nil {
- assert.NoError(t, err)
- }
- if tt.res.err != nil && !tt.res.err(err) {
- t.Errorf("got wrong err: %v ", err)
- }
- })
- }
-}
-
-func newMailTextChangedEvent(ctx context.Context, orgID, mailTextType, language, title, preHeader, subject, greeting, text, buttonText string) *org.MailTextChangedEvent {
- event, _ := org.NewMailTextChangedEvent(ctx,
- &org.NewAggregate(orgID, orgID).Aggregate,
- mailTextType,
- language,
- []policy.MailTextChanges{
- policy.ChangeTitle(title),
- policy.ChangePreHeader(preHeader),
- policy.ChangeSubject(subject),
- policy.ChangeGreeting(greeting),
- policy.ChangeText(text),
- policy.ChangeButtonText(buttonText),
- },
- )
- return event
-}
diff --git a/internal/command/org_policy_password_age.go b/internal/command/org_policy_password_age.go
index 48fa754f44..045fa35522 100644
--- a/internal/command/org_policy_password_age.go
+++ b/internal/command/org_policy_password_age.go
@@ -65,7 +65,7 @@ func (c *Commands) ChangePasswordAgePolicy(ctx context.Context, resourceOwner st
func (c *Commands) RemovePasswordAgePolicy(ctx context.Context, orgID string) (*domain.ObjectDetails, error) {
if orgID == "" {
- return nil, caos_errs.ThrowInvalidArgument(nil, "Org-2N8fs", "Errors.ResourceOwnerMissing")
+ return nil, caos_errs.ThrowInvalidArgument(nil, "Org-M58wd", "Errors.ResourceOwnerMissing")
}
existingPolicy := NewOrgPasswordAgePolicyWriteModel(orgID)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
diff --git a/internal/command/policy_mail_text_model.go b/internal/command/policy_mail_text_model.go
deleted file mode 100644
index 2d112bab8f..0000000000
--- a/internal/command/policy_mail_text_model.go
+++ /dev/null
@@ -1,65 +0,0 @@
-package command
-
-import (
- "github.com/caos/zitadel/internal/domain"
- "github.com/caos/zitadel/internal/eventstore"
- "github.com/caos/zitadel/internal/repository/policy"
-)
-
-type MailTextWriteModel struct {
- eventstore.WriteModel
-
- MailTextType string
- Language string
- Title string
- PreHeader string
- Subject string
- Greeting string
- Text string
- ButtonText string
-
- State domain.PolicyState
-}
-
-func (wm *MailTextWriteModel) Reduce() error {
- for _, event := range wm.Events {
- switch e := event.(type) {
- case *policy.MailTextAddedEvent:
- if wm.MailTextType != e.MailTextType || wm.Language != e.Language {
- continue
- }
- wm.Title = e.Title
- wm.PreHeader = e.PreHeader
- wm.Subject = e.Subject
- wm.Greeting = e.Greeting
- wm.Text = e.Text
- wm.ButtonText = e.ButtonText
- wm.State = domain.PolicyStateActive
- case *policy.MailTextChangedEvent:
- if wm.MailTextType != e.MailTextType || wm.Language != e.Language {
- continue
- }
- if e.Title != nil {
- wm.Title = *e.Title
- }
- if e.PreHeader != nil {
- wm.PreHeader = *e.PreHeader
- }
- if e.Subject != nil {
- wm.Subject = *e.Subject
- }
- if e.Greeting != nil {
- wm.Greeting = *e.Greeting
- }
- if e.Text != nil {
- wm.Text = *e.Text
- }
- if e.ButtonText != nil {
- wm.ButtonText = *e.ButtonText
- }
- case *policy.MailTextRemovedEvent:
- wm.State = domain.PolicyStateRemoved
- }
- }
- return wm.WriteModel.Reduce()
-}
diff --git a/internal/command/setup_step10.go b/internal/command/setup_step10.go
index d442b60f70..a1d5e6550f 100644
--- a/internal/command/setup_step10.go
+++ b/internal/command/setup_step10.go
@@ -2,14 +2,15 @@ package command
import (
"context"
+
"github.com/caos/logging"
+
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
)
type Step10 struct {
DefaultMailTemplate domain.MailTemplate
- DefaultMailTexts []domain.MailText
}
func (s *Step10) Step() domain.Step {
@@ -30,13 +31,6 @@ func (c *Commands) SetupStep10(ctx context.Context, step *Step10) error {
events := []eventstore.EventPusher{
mailTemplateEvent,
}
- for _, text := range step.DefaultMailTexts {
- defaultTextEvent, err := c.addDefaultMailText(ctx, iamAgg, NewIAMMailTextWriteModel(text.MailTextType, text.Language), &text)
- if err != nil {
- return nil, err
- }
- events = append(events, defaultTextEvent)
- }
logging.Log("SETUP-3N9fs").Info("default mail template/text set up")
return events, nil
}
diff --git a/internal/command/setup_step16.go b/internal/command/setup_step16.go
new file mode 100644
index 0000000000..35e1ab47be
--- /dev/null
+++ b/internal/command/setup_step16.go
@@ -0,0 +1,39 @@
+package command
+
+import (
+ "context"
+ "github.com/caos/logging"
+ "github.com/caos/zitadel/internal/domain"
+ "github.com/caos/zitadel/internal/eventstore"
+)
+
+type Step16 struct {
+ DefaultMessageTexts []domain.CustomMessageText
+}
+
+func (s *Step16) Step() domain.Step {
+ return domain.Step16
+}
+
+func (s *Step16) execute(ctx context.Context, commandSide *Commands) error {
+ return commandSide.SetupStep16(ctx, s)
+}
+
+func (c *Commands) SetupStep16(ctx context.Context, step *Step16) error {
+ fn := func(iam *IAMWriteModel) ([]eventstore.EventPusher, error) {
+ iamAgg := IAMAggregateFromWriteModel(&iam.WriteModel)
+ events := make([]eventstore.EventPusher, 0)
+
+ for _, text := range step.DefaultMessageTexts {
+ mailEvents, _, err := c.setDefaultMessageText(ctx, iamAgg, &text)
+ if err != nil {
+ return nil, err
+ }
+ events = append(events, mailEvents...)
+ }
+
+ logging.Log("SETUP-4k0LL").Info("default message text set up")
+ return events, nil
+ }
+ return c.setup(ctx, step, fn)
+}
diff --git a/internal/domain/custom_messge_text.go b/internal/domain/custom_messge_text.go
new file mode 100644
index 0000000000..744118e37f
--- /dev/null
+++ b/internal/domain/custom_messge_text.go
@@ -0,0 +1,42 @@
+package domain
+
+import (
+ "golang.org/x/text/language"
+
+ "github.com/caos/zitadel/internal/eventstore/v1/models"
+)
+
+const (
+ InitCodeMessageType = "InitCode"
+ PasswordResetMessageType = "PasswordReset"
+ VerifyEmailMessageType = "VerifyEmail"
+ VerifyPhoneMessageType = "VerifyPhone"
+ DomainClaimedMessageType = "DomainClaimed"
+ MessageTitle = "Title"
+ MessagePreHeader = "PreHeader"
+ MessageSubject = "Subject"
+ MessageGreeting = "Greeting"
+ MessageText = "Text"
+ MessageButtonText = "ButtonText"
+ MessageFooterText = "FooterText"
+)
+
+type CustomMessageText struct {
+ models.ObjectRoot
+
+ State PolicyState
+ Default bool
+ MessageTextType string
+ Language language.Tag
+ Title string
+ PreHeader string
+ Subject string
+ Greeting string
+ Text string
+ ButtonText string
+ FooterText string
+}
+
+func (m *CustomMessageText) IsValid() bool {
+ return m.MessageTextType != "" && m.Language != language.Und
+}
diff --git a/internal/domain/custom_text.go b/internal/domain/custom_text.go
new file mode 100644
index 0000000000..eb95df83e8
--- /dev/null
+++ b/internal/domain/custom_text.go
@@ -0,0 +1,32 @@
+package domain
+
+import (
+ "golang.org/x/text/language"
+
+ "github.com/caos/zitadel/internal/eventstore/v1/models"
+)
+
+type CustomText struct {
+ models.ObjectRoot
+
+ State CustomTextState
+ Default bool
+ Template string
+ Key string
+ Language language.Tag
+ Text string
+}
+
+type CustomTextState int32
+
+const (
+ CustomTextStateUnspecified CustomTextState = iota
+ CustomTextStateActive
+ CustomTextStateRemoved
+
+ customTextStateCount
+)
+
+func (m *CustomText) IsValid() bool {
+ return m.Key != "" && m.Language != language.Und && m.Text != ""
+}
diff --git a/internal/domain/features.go b/internal/domain/features.go
index e1dfeb84d2..ff49486e19 100644
--- a/internal/domain/features.go
+++ b/internal/domain/features.go
@@ -18,6 +18,7 @@ const (
FeatureLabelPolicy = "label_policy"
FeatureLabelPolicyPrivateLabel = FeatureLabelPolicy + ".private_label"
FeatureLabelPolicyWatermark = FeatureLabelPolicy + ".watermark"
+ FeatureCustomText = "custom_text"
FeatureCustomDomain = "custom_domain"
)
@@ -41,6 +42,7 @@ type Features struct {
LabelPolicyPrivateLabel bool
LabelPolicyWatermark bool
CustomDomain bool
+ CustomText bool
}
type FeaturesState int32
diff --git a/internal/domain/policy_mail_text.go b/internal/domain/policy_mail_text.go
deleted file mode 100644
index ce1e4b12b1..0000000000
--- a/internal/domain/policy_mail_text.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package domain
-
-import "github.com/caos/zitadel/internal/eventstore/v1/models"
-
-type MailText struct {
- models.ObjectRoot
-
- State PolicyState
- Default bool
- MailTextType string
- Language string
- Title string
- PreHeader string
- Subject string
- Greeting string
- Text string
- ButtonText string
-}
-
-func (m *MailText) IsValid() bool {
- return m.MailTextType != "" && m.Language != "" && m.Title != "" && m.PreHeader != "" && m.Subject != "" && m.Greeting != "" && m.Text != "" && m.ButtonText != ""
-}
diff --git a/internal/domain/step.go b/internal/domain/step.go
index 62d7edbac0..f93642cf8f 100644
--- a/internal/domain/step.go
+++ b/internal/domain/step.go
@@ -18,6 +18,7 @@ const (
Step13
Step14
Step15
+ Step16
//StepCount marks the the length of possible steps (StepCount-1 == last possible step)
StepCount
)
diff --git a/internal/features/model/features_view.go b/internal/features/model/features_view.go
index e72c9bf982..d2fdf24c3e 100644
--- a/internal/features/model/features_view.go
+++ b/internal/features/model/features_view.go
@@ -28,6 +28,7 @@ type FeaturesView struct {
LabelPolicyPrivateLabel bool
LabelPolicyWatermark bool
CustomDomain bool
+ CustomText bool
}
func (f *FeaturesView) FeatureList() []string {
@@ -62,6 +63,9 @@ func (f *FeaturesView) FeatureList() []string {
if f.CustomDomain {
list = append(list, domain.FeatureCustomDomain)
}
+ if f.CustomText {
+ list = append(list, domain.FeatureCustomText)
+ }
return list
}
diff --git a/internal/features/repository/view/model/features.go b/internal/features/repository/view/model/features.go
index 1093ed05c2..ca8dc1af38 100644
--- a/internal/features/repository/view/model/features.go
+++ b/internal/features/repository/view/model/features.go
@@ -42,6 +42,7 @@ type FeaturesView struct {
LabelPolicyPrivateLabel bool `json:"labelPolicyPrivateLabel" gorm:"column:label_policy_private_label"`
LabelPolicyWatermark bool `json:"labelPolicyWatermark" gorm:"column:label_policy_watermark"`
CustomDomain bool `json:"customDomain" gorm:"column:custom_domain"`
+ CustomText bool `json:"customText" gorm:"column:custom_text"`
}
func FeaturesToModel(features *FeaturesView) *features_model.FeaturesView {
@@ -66,6 +67,7 @@ func FeaturesToModel(features *FeaturesView) *features_model.FeaturesView {
LabelPolicyPrivateLabel: features.LabelPolicyPrivateLabel,
LabelPolicyWatermark: features.LabelPolicyWatermark,
CustomDomain: features.CustomDomain,
+ CustomText: features.CustomText,
}
}
diff --git a/internal/iam/model/mail_text.go b/internal/iam/model/mail_text.go
index 353ab70f1f..6a74587e6b 100644
--- a/internal/iam/model/mail_text.go
+++ b/internal/iam/model/mail_text.go
@@ -21,6 +21,7 @@ type MailText struct {
Greeting string
Text string
ButtonText string
+ FooterText string
}
func (p *MailText) IsValid() bool {
diff --git a/internal/iam/model/mail_text_view.go b/internal/iam/model/mail_text_view.go
deleted file mode 100644
index ccc27e7522..0000000000
--- a/internal/iam/model/mail_text_view.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package model
-
-import (
- "github.com/caos/zitadel/internal/domain"
- "time"
-)
-
-type MailTextsView struct {
- Texts []*MailTextView
- Default bool
-}
-type MailTextView struct {
- AggregateID string
- MailTextType string
- Language string
- Title string
- PreHeader string
- Subject string
- Greeting string
- Text string
- ButtonText string
- Default bool
-
- CreationDate time.Time
- ChangeDate time.Time
- Sequence uint64
-}
-
-type MailTextSearchRequest struct {
- Offset uint64
- Limit uint64
- SortingColumn MailTextSearchKey
- Asc bool
- Queries []*MailTextSearchQuery
-}
-
-type MailTextSearchKey int32
-
-const (
- MailTextSearchKeyUnspecified MailTextSearchKey = iota
- MailTextSearchKeyAggregateID
- MailTextSearchKeyMailTextType
- MailTextSearchKeyLanguage
-)
-
-type MailTextSearchQuery struct {
- Key MailTextSearchKey
- Method domain.SearchMethod
- Value interface{}
-}
-
-type MailTextSearchResponse struct {
- Offset uint64
- Limit uint64
- TotalResult uint64
- Result []*MailTextView
- Sequence uint64
- Timestamp time.Time
-}
diff --git a/internal/iam/model/message_text_view.go b/internal/iam/model/message_text_view.go
new file mode 100644
index 0000000000..86b42cc7a7
--- /dev/null
+++ b/internal/iam/model/message_text_view.go
@@ -0,0 +1,63 @@
+package model
+
+import (
+ "time"
+
+ "golang.org/x/text/language"
+
+ "github.com/caos/zitadel/internal/domain"
+)
+
+type MessageTextsView struct {
+ Texts []*MessageTextView
+ Default bool
+}
+type MessageTextView struct {
+ AggregateID string
+ MessageTextType string
+ Language language.Tag
+ Title string
+ PreHeader string
+ Subject string
+ Greeting string
+ Text string
+ ButtonText string
+ FooterText string
+ Default bool
+
+ CreationDate time.Time
+ ChangeDate time.Time
+ Sequence uint64
+}
+
+type MessageTextSearchRequest struct {
+ Offset uint64
+ Limit uint64
+ SortingColumn MessageTextSearchKey
+ Asc bool
+ Queries []*MessageTextSearchQuery
+}
+
+type MessageTextSearchKey int32
+
+const (
+ MessageTextSearchKeyUnspecified MessageTextSearchKey = iota
+ MessageTextSearchKeyAggregateID
+ MessageTextSearchKeyMessageTextType
+ MessageTextSearchKeyLanguage
+)
+
+type MessageTextSearchQuery struct {
+ Key MessageTextSearchKey
+ Method domain.SearchMethod
+ Value interface{}
+}
+
+type MessageTextSearchResponse struct {
+ Offset uint64
+ Limit uint64
+ TotalResult uint64
+ Result []*MessageTextView
+ Sequence uint64
+ Timestamp time.Time
+}
diff --git a/internal/iam/repository/eventsourcing/model/iam.go b/internal/iam/repository/eventsourcing/model/iam.go
index 2d14dd37f2..d7867762d8 100644
--- a/internal/iam/repository/eventsourcing/model/iam.go
+++ b/internal/iam/repository/eventsourcing/model/iam.go
@@ -33,64 +33,23 @@ type IAM struct {
DefaultLoginPolicy *LoginPolicy `json:"-"`
DefaultLabelPolicy *LabelPolicy `json:"-"`
DefaultMailTemplate *MailTemplate `json:"-"`
- DefaultMailTexts []*MailText `json:"-"`
DefaultOrgIAMPolicy *OrgIAMPolicy `json:"-"`
DefaultPasswordComplexityPolicy *PasswordComplexityPolicy `json:"-"`
DefaultPasswordAgePolicy *PasswordAgePolicy `json:"-"`
DefaultPasswordLockoutPolicy *PasswordLockoutPolicy `json:"-"`
}
-func IAMFromModel(iam *model.IAM) *IAM {
- members := IAMMembersFromModel(iam.Members)
- idps := IDPConfigsFromModel(iam.IDPs)
- mailTexts := MailTextsFromModel(iam.DefaultMailTexts)
- converted := &IAM{
- ObjectRoot: iam.ObjectRoot,
- SetUpStarted: Step(iam.SetUpStarted),
- SetUpDone: Step(iam.SetUpDone),
- GlobalOrgID: iam.GlobalOrgID,
- IAMProjectID: iam.IAMProjectID,
- Members: members,
- IDPs: idps,
- DefaultMailTexts: mailTexts,
- }
- if iam.DefaultLoginPolicy != nil {
- converted.DefaultLoginPolicy = LoginPolicyFromModel(iam.DefaultLoginPolicy)
- }
- if iam.DefaultLabelPolicy != nil {
- converted.DefaultLabelPolicy = LabelPolicyFromModel(iam.DefaultLabelPolicy)
- }
- if iam.DefaultMailTemplate != nil {
- converted.DefaultMailTemplate = MailTemplateFromModel(iam.DefaultMailTemplate)
- }
- if iam.DefaultPasswordComplexityPolicy != nil {
- converted.DefaultPasswordComplexityPolicy = PasswordComplexityPolicyFromModel(iam.DefaultPasswordComplexityPolicy)
- }
- if iam.DefaultPasswordAgePolicy != nil {
- converted.DefaultPasswordAgePolicy = PasswordAgePolicyFromModel(iam.DefaultPasswordAgePolicy)
- }
- if iam.DefaultPasswordLockoutPolicy != nil {
- converted.DefaultPasswordLockoutPolicy = PasswordLockoutPolicyFromModel(iam.DefaultPasswordLockoutPolicy)
- }
- if iam.DefaultOrgIAMPolicy != nil {
- converted.DefaultOrgIAMPolicy = OrgIAMPolicyFromModel(iam.DefaultOrgIAMPolicy)
- }
- return converted
-}
-
func IAMToModel(iam *IAM) *model.IAM {
members := IAMMembersToModel(iam.Members)
idps := IDPConfigsToModel(iam.IDPs)
- mailTexts := MailTextsToModel(iam.DefaultMailTexts)
converted := &model.IAM{
- ObjectRoot: iam.ObjectRoot,
- SetUpStarted: domain.Step(iam.SetUpStarted),
- SetUpDone: domain.Step(iam.SetUpDone),
- GlobalOrgID: iam.GlobalOrgID,
- IAMProjectID: iam.IAMProjectID,
- Members: members,
- IDPs: idps,
- DefaultMailTexts: mailTexts,
+ ObjectRoot: iam.ObjectRoot,
+ SetUpStarted: domain.Step(iam.SetUpStarted),
+ SetUpDone: domain.Step(iam.SetUpDone),
+ GlobalOrgID: iam.GlobalOrgID,
+ IAMProjectID: iam.IAMProjectID,
+ Members: members,
+ IDPs: idps,
}
if iam.DefaultLoginPolicy != nil {
converted.DefaultLoginPolicy = LoginPolicyToModel(iam.DefaultLoginPolicy)
@@ -199,10 +158,6 @@ func (i *IAM) AppendEvent(event *es_models.Event) (err error) {
return i.appendAddMailTemplateEvent(event)
case MailTemplateChanged:
return i.appendChangeMailTemplateEvent(event)
- case MailTextAdded:
- return i.appendAddMailTextEvent(event)
- case MailTextChanged:
- return i.appendChangeMailTextEvent(event)
case PasswordComplexityPolicyAdded:
return i.appendAddPasswordComplexityPolicyEvent(event)
case PasswordComplexityPolicyChanged:
diff --git a/internal/iam/repository/eventsourcing/model/mail_text.go b/internal/iam/repository/eventsourcing/model/mail_text.go
index 27b85fef0a..853134a532 100644
--- a/internal/iam/repository/eventsourcing/model/mail_text.go
+++ b/internal/iam/repository/eventsourcing/model/mail_text.go
@@ -110,43 +110,6 @@ func (p *MailText) Changes(changed *MailText) map[string]interface{} {
return changes
}
-func (i *IAM) appendAddMailTextEvent(event *es_models.Event) error {
- mailText := &MailText{}
- err := mailText.SetDataLabel(event)
- if err != nil {
- return err
- }
- mailText.ObjectRoot.CreationDate = event.CreationDate
- i.DefaultMailTexts = append(i.DefaultMailTexts, mailText)
- return nil
-}
-
-func (i *IAM) appendChangeMailTextEvent(event *es_models.Event) error {
- mailText := &MailText{}
- err := mailText.SetDataLabel(event)
- if err != nil {
- return err
- }
- if n, m := GetMailText(i.DefaultMailTexts, mailText.MailTextType, mailText.Language); m != nil {
- i.DefaultMailTexts[n] = mailText
- }
- return nil
-}
-
-func (i *IAM) appendRemoveMailTextEvent(event *es_models.Event) error {
- mailText := &MailText{}
- err := mailText.SetDataLabel(event)
- if err != nil {
- return err
- }
- if n, m := GetMailText(i.DefaultMailTexts, mailText.MailTextType, mailText.Language); m != nil {
- i.DefaultMailTexts[n] = i.DefaultMailTexts[len(i.DefaultMailTexts)-1]
- i.DefaultMailTexts[len(i.DefaultMailTexts)-1] = nil
- i.DefaultMailTexts = i.DefaultMailTexts[:len(i.DefaultMailTexts)-1]
- }
- return nil
-}
-
func (p *MailText) SetDataLabel(event *es_models.Event) error {
err := json.Unmarshal(event.Data, p)
if err != nil {
diff --git a/internal/iam/repository/eventsourcing/model/mail_text_test.go b/internal/iam/repository/eventsourcing/model/mail_text_test.go
deleted file mode 100644
index 557be25d55..0000000000
--- a/internal/iam/repository/eventsourcing/model/mail_text_test.go
+++ /dev/null
@@ -1,134 +0,0 @@
-package model
-
-import (
- "encoding/json"
- "testing"
-
- es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
-)
-
-func TestAppendAddMailTextEvent(t *testing.T) {
- type args struct {
- iam *IAM
- mailText *MailText
- event *es_models.Event
- }
- tests := []struct {
- name string
- args args
- result *IAM
- }{
- {
- name: "append add mailText event",
- args: args{
- iam: &IAM{},
- mailText: &MailText{
- MailTextType: "PasswordReset",
- Language: "DE"},
- event: &es_models.Event{},
- },
- result: &IAM{DefaultMailTexts: []*MailText{&MailText{
- MailTextType: "PasswordReset",
- Language: "DE"}}},
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if tt.args.mailText != nil {
- data, _ := json.Marshal(tt.args.mailText)
- tt.args.event.Data = data
- }
- tt.args.iam.appendAddMailTextEvent(tt.args.event)
- if len(tt.args.iam.DefaultMailTexts) != 1 {
- t.Errorf("got wrong result should have one mailText actual: %v ", len(tt.args.iam.DefaultMailTexts))
- }
- if tt.args.iam.DefaultMailTexts[0] == tt.result.DefaultMailTexts[0] {
- t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultMailTexts[0], tt.args.iam.DefaultMailTexts[0])
- }
- })
- }
-}
-
-func TestAppendChangeMailTextEvent(t *testing.T) {
- type args struct {
- iam *IAM
- mailText *MailText
- event *es_models.Event
- }
- tests := []struct {
- name string
- args args
- result *IAM
- }{
- {
- name: "append change mailText event",
- args: args{
- iam: &IAM{DefaultMailTexts: []*MailText{&MailText{
- MailTextType: "PasswordReset",
- Language: "DE"}}},
- mailText: &MailText{
- MailTextType: "ChangedPasswordReset",
- Language: "DE"},
- event: &es_models.Event{},
- },
- result: &IAM{DefaultMailTexts: []*MailText{&MailText{
- MailTextType: "PasswordReset",
- Language: "ChangedDE"}}},
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if tt.args.mailText != nil {
- data, _ := json.Marshal(tt.args.mailText)
- tt.args.event.Data = data
- }
- tt.args.iam.appendChangeMailTextEvent(tt.args.event)
- if len(tt.args.iam.DefaultMailTexts) != 1 {
- t.Errorf("got wrong result should have one mailText actual: %v ", len(tt.args.iam.DefaultMailTexts))
- }
- if tt.args.iam.DefaultMailTexts[0] == tt.result.DefaultMailTexts[0] {
- t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultMailTexts[0], tt.args.iam.DefaultMailTexts[0])
- }
- })
- }
-}
-
-func TestAppendRemoveMailTextEvent(t *testing.T) {
- type args struct {
- iam *IAM
- mailText *MailText
- event *es_models.Event
- }
- tests := []struct {
- name string
- args args
- result *IAM
- }{
- {
- name: "append remove mailText event",
- args: args{
- iam: &IAM{DefaultMailTexts: []*MailText{&MailText{
- MailTextType: "PasswordReset",
- Language: "DE",
- Subject: "Subject"}}},
- mailText: &MailText{
- MailTextType: "PasswordReset",
- Language: "DE"},
- event: &es_models.Event{},
- },
- result: &IAM{DefaultMailTexts: []*MailText{}},
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if tt.args.mailText != nil {
- data, _ := json.Marshal(tt.args.mailText)
- tt.args.event.Data = data
- }
- tt.args.iam.appendRemoveMailTextEvent(tt.args.event)
- if len(tt.args.iam.DefaultMailTexts) != 0 {
- t.Errorf("got wrong result should have no mailText actual: %v ", len(tt.args.iam.DefaultMailTexts))
- }
- })
- }
-}
diff --git a/internal/iam/repository/eventsourcing/model/types.go b/internal/iam/repository/eventsourcing/model/types.go
index 46a0cc4af4..d538aaed52 100644
--- a/internal/iam/repository/eventsourcing/model/types.go
+++ b/internal/iam/repository/eventsourcing/model/types.go
@@ -54,8 +54,9 @@ const (
MailTemplateAdded models.EventType = "iam.mail.template.added"
MailTemplateChanged models.EventType = "iam.mail.template.changed"
- MailTextAdded models.EventType = "iam.mail.text.added"
- MailTextChanged models.EventType = "iam.mail.text.changed"
+
+ CustomTextSet models.EventType = "iam.customtext.set"
+ CustomTextRemoved models.EventType = "iam.customtext.removed"
PasswordComplexityPolicyAdded models.EventType = "iam.policy.password.complexity.added"
PasswordComplexityPolicyChanged models.EventType = "iam.policy.password.complexity.changed"
diff --git a/internal/iam/repository/view/mail_text_view.go b/internal/iam/repository/view/mail_text_view.go
deleted file mode 100644
index ceb7ece1b6..0000000000
--- a/internal/iam/repository/view/mail_text_view.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package view
-
-import (
- "github.com/caos/zitadel/internal/domain"
- caos_errs "github.com/caos/zitadel/internal/errors"
- iam_model "github.com/caos/zitadel/internal/iam/model"
- "github.com/caos/zitadel/internal/iam/repository/view/model"
- "github.com/caos/zitadel/internal/view/repository"
- "github.com/jinzhu/gorm"
- "strings"
-)
-
-func GetMailTexts(db *gorm.DB, table string, aggregateID string) ([]*model.MailTextView, error) {
- texts := make([]*model.MailTextView, 0)
- queries := []*iam_model.MailTextSearchQuery{
- {
- Key: iam_model.MailTextSearchKeyAggregateID,
- Value: aggregateID,
- Method: domain.SearchMethodEquals,
- },
- }
- query := repository.PrepareSearchQuery(table, model.MailTextSearchRequest{Queries: queries})
- _, err := query(db, &texts)
- if err != nil {
- return nil, err
- }
- return texts, nil
-}
-
-func GetMailTextByIDs(db *gorm.DB, table, aggregateID string, textType string, language string) (*model.MailTextView, error) {
- mailText := new(model.MailTextView)
- aggregateIDQuery := &model.MailTextSearchQuery{Key: iam_model.MailTextSearchKeyAggregateID, Value: aggregateID, Method: domain.SearchMethodEquals}
- textTypeQuery := &model.MailTextSearchQuery{Key: iam_model.MailTextSearchKeyMailTextType, Value: textType, Method: domain.SearchMethodEquals}
- languageQuery := &model.MailTextSearchQuery{Key: iam_model.MailTextSearchKeyLanguage, Value: strings.ToUpper(language), Method: domain.SearchMethodEquals}
- query := repository.PrepareGetByQuery(table, aggregateIDQuery, textTypeQuery, languageQuery)
- err := query(db, mailText)
- if caos_errs.IsNotFound(err) {
- return nil, caos_errs.ThrowNotFound(nil, "VIEW-IiJjm", "Errors.IAM.MailText.NotExisting")
- }
- return mailText, err
-}
-
-func PutMailText(db *gorm.DB, table string, mailText *model.MailTextView) error {
- save := repository.PrepareSave(table)
- return save(db, mailText)
-}
-
-func DeleteMailText(db *gorm.DB, table, aggregateID string, textType string, language string) error {
- aggregateIDSearch := repository.Key{Key: model.MailTextSearchKey(iam_model.MailTextSearchKeyAggregateID), Value: aggregateID}
- textTypeSearch := repository.Key{Key: model.MailTextSearchKey(iam_model.MailTextSearchKeyMailTextType), Value: textType}
- languageSearch := repository.Key{Key: model.MailTextSearchKey(iam_model.MailTextSearchKeyLanguage), Value: language}
- delete := repository.PrepareDeleteByKeys(table, aggregateIDSearch, textTypeSearch, languageSearch)
- return delete(db)
-}
diff --git a/internal/iam/repository/view/message_text_view.go b/internal/iam/repository/view/message_text_view.go
new file mode 100644
index 0000000000..345664904f
--- /dev/null
+++ b/internal/iam/repository/view/message_text_view.go
@@ -0,0 +1,54 @@
+package view
+
+import (
+ "github.com/jinzhu/gorm"
+
+ "github.com/caos/zitadel/internal/domain"
+ caos_errs "github.com/caos/zitadel/internal/errors"
+ iam_model "github.com/caos/zitadel/internal/iam/model"
+ "github.com/caos/zitadel/internal/iam/repository/view/model"
+ "github.com/caos/zitadel/internal/view/repository"
+)
+
+func GetMessageTexts(db *gorm.DB, table string, aggregateID string) ([]*model.MessageTextView, error) {
+ texts := make([]*model.MessageTextView, 0)
+ queries := []*iam_model.MessageTextSearchQuery{
+ {
+ Key: iam_model.MessageTextSearchKeyAggregateID,
+ Value: aggregateID,
+ Method: domain.SearchMethodEquals,
+ },
+ }
+ query := repository.PrepareSearchQuery(table, model.MessageTextSearchRequest{Queries: queries})
+ _, err := query(db, &texts)
+ if err != nil {
+ return nil, err
+ }
+ return texts, nil
+}
+
+func GetMessageTextByIDs(db *gorm.DB, table, aggregateID, textType, lang string) (*model.MessageTextView, error) {
+ mailText := new(model.MessageTextView)
+ aggregateIDQuery := &model.MessageTextSearchQuery{Key: iam_model.MessageTextSearchKeyAggregateID, Value: aggregateID, Method: domain.SearchMethodEquals}
+ textTypeQuery := &model.MessageTextSearchQuery{Key: iam_model.MessageTextSearchKeyMessageTextType, Value: textType, Method: domain.SearchMethodEquals}
+ languageQuery := &model.MessageTextSearchQuery{Key: iam_model.MessageTextSearchKeyLanguage, Value: lang, Method: domain.SearchMethodEquals}
+ query := repository.PrepareGetByQuery(table, aggregateIDQuery, textTypeQuery, languageQuery)
+ err := query(db, mailText)
+ if caos_errs.IsNotFound(err) {
+ return nil, caos_errs.ThrowNotFound(nil, "VIEW-IiJjm", "Errors.IAM.CustomMessageText.NotExisting")
+ }
+ return mailText, err
+}
+
+func PutMessageText(db *gorm.DB, table string, mailText *model.MessageTextView) error {
+ save := repository.PrepareSave(table)
+ return save(db, mailText)
+}
+
+func DeleteMessageText(db *gorm.DB, table, aggregateID, textType, lang string) error {
+ aggregateIDSearch := repository.Key{Key: model.MessageTextSearchKey(iam_model.MessageTextSearchKeyAggregateID), Value: aggregateID}
+ textTypeSearch := repository.Key{Key: model.MessageTextSearchKey(iam_model.MessageTextSearchKeyMessageTextType), Value: textType}
+ languageSearch := repository.Key{Key: model.MessageTextSearchKey(iam_model.MessageTextSearchKeyLanguage), Value: lang}
+ delete := repository.PrepareDeleteByKeys(table, aggregateIDSearch, textTypeSearch, languageSearch)
+ return delete(db)
+}
diff --git a/internal/iam/repository/view/model/mail_text.go b/internal/iam/repository/view/model/mail_text.go
deleted file mode 100644
index 6812ec8473..0000000000
--- a/internal/iam/repository/view/model/mail_text.go
+++ /dev/null
@@ -1,117 +0,0 @@
-package model
-
-import (
- "encoding/json"
- "time"
-
- org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
-
- es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
-
- "github.com/caos/logging"
- caos_errs "github.com/caos/zitadel/internal/errors"
- "github.com/caos/zitadel/internal/eventstore/v1/models"
- "github.com/caos/zitadel/internal/iam/model"
-)
-
-const (
- MailTextKeyAggregateID = "aggregate_id"
- MailTextKeyMailTextType = "mail_text_type"
- MailTextKeyLanguage = "language"
-)
-
-type MailTextView struct {
- AggregateID string `json:"-" gorm:"column:aggregate_id;primary_key"`
- CreationDate time.Time `json:"-" gorm:"column:creation_date"`
- ChangeDate time.Time `json:"-" gorm:"column:change_date"`
- State int32 `json:"-" gorm:"column:mail_text_state"`
-
- MailTextType string `json:"mailTextType" gorm:"column:mail_text_type;primary_key"`
- Language string `json:"language" gorm:"column:language;primary_key"`
- Title string `json:"title" gorm:"column:title"`
- PreHeader string `json:"preHeader" gorm:"column:pre_header"`
- Subject string `json:"subject" gorm:"column:subject"`
- Greeting string `json:"greeting" gorm:"column:greeting"`
- Text string `json:"text" gorm:"column:text"`
- ButtonText string `json:"buttonText" gorm:"column:button_text"`
- Default bool `json:"-" gorm:"-"`
-
- Sequence uint64 `json:"-" gorm:"column:sequence"`
-}
-
-func MailTextViewFromModel(template *model.MailTextView) *MailTextView {
- return &MailTextView{
- AggregateID: template.AggregateID,
- Sequence: template.Sequence,
- CreationDate: template.CreationDate,
- ChangeDate: template.ChangeDate,
- MailTextType: template.MailTextType,
- Language: template.Language,
- Title: template.Title,
- PreHeader: template.PreHeader,
- Subject: template.Subject,
- Greeting: template.Greeting,
- Text: template.Text,
- ButtonText: template.ButtonText,
- Default: template.Default,
- }
-}
-
-func MailTextsViewToModel(textsIn []*MailTextView, defaultIn bool) *model.MailTextsView {
- return &model.MailTextsView{
- Texts: mailTextsViewToModelArr(textsIn, defaultIn),
- }
-}
-
-func mailTextsViewToModelArr(texts []*MailTextView, defaultIn bool) []*model.MailTextView {
- result := make([]*model.MailTextView, len(texts))
- for i, r := range texts {
- r.Default = defaultIn
- result[i] = MailTextViewToModel(r)
- }
- return result
-}
-
-func MailTextViewToModel(template *MailTextView) *model.MailTextView {
- return &model.MailTextView{
- AggregateID: template.AggregateID,
- Sequence: template.Sequence,
- CreationDate: template.CreationDate,
- ChangeDate: template.ChangeDate,
- MailTextType: template.MailTextType,
- Language: template.Language,
- Title: template.Title,
- PreHeader: template.PreHeader,
- Subject: template.Subject,
- Greeting: template.Greeting,
- Text: template.Text,
- ButtonText: template.ButtonText,
- Default: template.Default,
- }
-}
-
-func (i *MailTextView) AppendEvent(event *models.Event) (err error) {
- i.Sequence = event.Sequence
- switch event.Type {
- case es_model.MailTextAdded, org_es_model.MailTextAdded:
- i.setRootData(event)
- i.CreationDate = event.CreationDate
- err = i.SetData(event)
- case es_model.MailTextChanged, org_es_model.MailTextChanged:
- i.ChangeDate = event.CreationDate
- err = i.SetData(event)
- }
- return err
-}
-
-func (r *MailTextView) setRootData(event *models.Event) {
- r.AggregateID = event.AggregateID
-}
-
-func (r *MailTextView) SetData(event *models.Event) error {
- if err := json.Unmarshal(event.Data, r); err != nil {
- logging.Log("MODEL-UFqAG").WithError(err).Error("could not unmarshal event data")
- return caos_errs.ThrowInternal(err, "MODEL-5CVaR", "Could not unmarshal data")
- }
- return nil
-}
diff --git a/internal/iam/repository/view/model/mail_text_query.go b/internal/iam/repository/view/model/mail_text_query.go
deleted file mode 100644
index 6f3da80765..0000000000
--- a/internal/iam/repository/view/model/mail_text_query.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package model
-
-import (
- "github.com/caos/zitadel/internal/domain"
- iam_model "github.com/caos/zitadel/internal/iam/model"
- "github.com/caos/zitadel/internal/view/repository"
-)
-
-type MailTextSearchRequest iam_model.MailTextSearchRequest
-type MailTextSearchQuery iam_model.MailTextSearchQuery
-type MailTextSearchKey iam_model.MailTextSearchKey
-
-func (req MailTextSearchRequest) GetLimit() uint64 {
- return req.Limit
-}
-
-func (req MailTextSearchRequest) GetOffset() uint64 {
- return req.Offset
-}
-
-func (req MailTextSearchRequest) GetSortingColumn() repository.ColumnKey {
- if req.SortingColumn == iam_model.MailTextSearchKeyUnspecified {
- return nil
- }
- return MailTextSearchKey(req.SortingColumn)
-}
-
-func (req MailTextSearchRequest) GetAsc() bool {
- return req.Asc
-}
-
-func (req MailTextSearchRequest) GetQueries() []repository.SearchQuery {
- result := make([]repository.SearchQuery, len(req.Queries))
- for i, q := range req.Queries {
- result[i] = MailTextSearchQuery{Key: q.Key, Value: q.Value, Method: q.Method}
- }
- return result
-}
-
-func (req MailTextSearchQuery) GetKey() repository.ColumnKey {
- return MailTextSearchKey(req.Key)
-}
-
-func (req MailTextSearchQuery) GetMethod() domain.SearchMethod {
- return req.Method
-}
-
-func (req MailTextSearchQuery) GetValue() interface{} {
- return req.Value
-}
-
-func (key MailTextSearchKey) ToColumnName() string {
- switch iam_model.MailTextSearchKey(key) {
- case iam_model.MailTextSearchKeyAggregateID:
- return MailTextKeyAggregateID
- case iam_model.MailTextSearchKeyMailTextType:
- return MailTextKeyMailTextType
- case iam_model.MailTextSearchKeyLanguage:
- return MailTextKeyLanguage
- default:
- return ""
- }
-}
diff --git a/internal/iam/repository/view/model/message_text.go b/internal/iam/repository/view/model/message_text.go
new file mode 100644
index 0000000000..82253fdba5
--- /dev/null
+++ b/internal/iam/repository/view/model/message_text.go
@@ -0,0 +1,184 @@
+package model
+
+import (
+ "encoding/json"
+ "time"
+
+ "golang.org/x/text/language"
+
+ "github.com/caos/zitadel/internal/domain"
+ org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
+
+ es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
+
+ "github.com/caos/logging"
+
+ caos_errs "github.com/caos/zitadel/internal/errors"
+ "github.com/caos/zitadel/internal/eventstore/v1/models"
+ "github.com/caos/zitadel/internal/iam/model"
+)
+
+const (
+ MessageTextKeyAggregateID = "aggregate_id"
+ MessageTextKeyMessageTextType = "message_text_type"
+ MessageTextKeyLanguage = "language"
+)
+
+type MessageTextView struct {
+ AggregateID string `json:"-" gorm:"column:aggregate_id;primary_key"`
+ CreationDate time.Time `json:"-" gorm:"column:creation_date"`
+ ChangeDate time.Time `json:"-" gorm:"column:change_date"`
+ State int32 `json:"-" gorm:"column:message_text_state"`
+
+ MessageTextType string `json:"-" gorm:"column:message_text_type;primary_key"`
+ Language string `json:"-" gorm:"column:language;primary_key"`
+ Title string `json:"-" gorm:"column:title"`
+ PreHeader string `json:"-" gorm:"column:pre_header"`
+ Subject string `json:"-" gorm:"column:subject"`
+ Greeting string `json:"-" gorm:"column:greeting"`
+ Text string `json:"-" gorm:"column:text"`
+ ButtonText string `json:"-" gorm:"column:button_text"`
+ FooterText string `json:"-" gorm:"column:footer_text"`
+ Default bool `json:"-" gorm:"-"`
+
+ Sequence uint64 `json:"-" gorm:"column:sequence"`
+}
+
+func MessageTextViewFromModel(template *model.MessageTextView) *MessageTextView {
+ return &MessageTextView{
+ AggregateID: template.AggregateID,
+ Sequence: template.Sequence,
+ CreationDate: template.CreationDate,
+ ChangeDate: template.ChangeDate,
+ MessageTextType: template.MessageTextType,
+ Language: template.Language.String(),
+ Title: template.Title,
+ PreHeader: template.PreHeader,
+ Subject: template.Subject,
+ Greeting: template.Greeting,
+ Text: template.Text,
+ ButtonText: template.ButtonText,
+ FooterText: template.FooterText,
+ Default: template.Default,
+ }
+}
+
+func MessageTextsViewToModel(textsIn []*MessageTextView, defaultIn bool) *model.MessageTextsView {
+ return &model.MessageTextsView{
+ Texts: messageTextsViewToModelArr(textsIn, defaultIn),
+ }
+}
+
+func messageTextsViewToModelArr(texts []*MessageTextView, defaultIn bool) []*model.MessageTextView {
+ result := make([]*model.MessageTextView, len(texts))
+ for i, r := range texts {
+ r.Default = defaultIn
+ result[i] = MessageTextViewToModel(r)
+ }
+ return result
+}
+
+func MessageTextViewToModel(template *MessageTextView) *model.MessageTextView {
+ lang := language.Make(template.Language)
+ return &model.MessageTextView{
+ AggregateID: template.AggregateID,
+ Sequence: template.Sequence,
+ CreationDate: template.CreationDate,
+ ChangeDate: template.ChangeDate,
+ MessageTextType: template.MessageTextType,
+ Language: lang,
+ Title: template.Title,
+ PreHeader: template.PreHeader,
+ Subject: template.Subject,
+ Greeting: template.Greeting,
+ Text: template.Text,
+ ButtonText: template.ButtonText,
+ FooterText: template.FooterText,
+ Default: template.Default,
+ }
+}
+
+func (i *MessageTextView) AppendEvent(event *models.Event) (err error) {
+ i.Sequence = event.Sequence
+ switch event.Type {
+ case es_model.CustomTextSet, org_es_model.CustomTextSet:
+ i.setRootData(event)
+ customText := new(CustomText)
+ err = customText.SetData(event)
+ if err != nil {
+ return err
+ }
+ if customText.Key == domain.MessageTitle {
+ i.Title = customText.Text
+ }
+ if customText.Key == domain.MessagePreHeader {
+ i.PreHeader = customText.Text
+ }
+ if customText.Key == domain.MessageSubject {
+ i.Subject = customText.Text
+ }
+ if customText.Key == domain.MessageGreeting {
+ i.Greeting = customText.Text
+ }
+ if customText.Key == domain.MessageText {
+ i.Text = customText.Text
+ }
+ if customText.Key == domain.MessageButtonText {
+ i.ButtonText = customText.Text
+ }
+ if customText.Key == domain.MessageFooterText {
+ i.FooterText = customText.Text
+ }
+ i.ChangeDate = event.CreationDate
+ case es_model.CustomTextRemoved, org_es_model.CustomTextRemoved:
+ customText := new(CustomText)
+ err = customText.SetData(event)
+ if err != nil {
+ return err
+ }
+ if customText.Key == domain.MessageTitle {
+ i.Title = ""
+ }
+ if customText.Key == domain.MessagePreHeader {
+ i.PreHeader = ""
+ }
+ if customText.Key == domain.MessageSubject {
+ i.Subject = ""
+ }
+ if customText.Key == domain.MessageGreeting {
+ i.Greeting = ""
+ }
+ if customText.Key == domain.MessageText {
+ i.Text = ""
+ }
+ if customText.Key == domain.MessageButtonText {
+ i.ButtonText = ""
+ }
+ if customText.Key == domain.MessageFooterText {
+ i.FooterText = ""
+ }
+ i.ChangeDate = event.CreationDate
+ case org_es_model.CustomTextMessageRemoved:
+ i.State = int32(model.PolicyStateRemoved)
+ }
+ return err
+}
+
+func (r *MessageTextView) setRootData(event *models.Event) {
+ r.AggregateID = event.AggregateID
+}
+
+type CustomText struct {
+ Template string `json:"template"`
+ Key string `json:"key"`
+ Language language.Tag `json:"language"`
+ Text string `json:"text"`
+}
+
+func (r *CustomText) SetData(event *models.Event) error {
+ if err := json.Unmarshal(event.Data, r); err != nil {
+ logging.Log("MODEL-3n9fs").WithError(err).Error("could not unmarshal event data")
+ return caos_errs.ThrowInternal(err, "MODEL-5CVaR", "Could not unmarshal data")
+ }
+ return nil
+}
diff --git a/internal/iam/repository/view/model/message_text_query.go b/internal/iam/repository/view/model/message_text_query.go
new file mode 100644
index 0000000000..90f120ddd3
--- /dev/null
+++ b/internal/iam/repository/view/model/message_text_query.go
@@ -0,0 +1,63 @@
+package model
+
+import (
+ "github.com/caos/zitadel/internal/domain"
+ iam_model "github.com/caos/zitadel/internal/iam/model"
+ "github.com/caos/zitadel/internal/view/repository"
+)
+
+type MessageTextSearchRequest iam_model.MessageTextSearchRequest
+type MessageTextSearchQuery iam_model.MessageTextSearchQuery
+type MessageTextSearchKey iam_model.MessageTextSearchKey
+
+func (req MessageTextSearchRequest) GetLimit() uint64 {
+ return req.Limit
+}
+
+func (req MessageTextSearchRequest) GetOffset() uint64 {
+ return req.Offset
+}
+
+func (req MessageTextSearchRequest) GetSortingColumn() repository.ColumnKey {
+ if req.SortingColumn == iam_model.MessageTextSearchKeyUnspecified {
+ return nil
+ }
+ return MessageTextSearchKey(req.SortingColumn)
+}
+
+func (req MessageTextSearchRequest) GetAsc() bool {
+ return req.Asc
+}
+
+func (req MessageTextSearchRequest) GetQueries() []repository.SearchQuery {
+ result := make([]repository.SearchQuery, len(req.Queries))
+ for i, q := range req.Queries {
+ result[i] = MessageTextSearchQuery{Key: q.Key, Value: q.Value, Method: q.Method}
+ }
+ return result
+}
+
+func (req MessageTextSearchQuery) GetKey() repository.ColumnKey {
+ return MessageTextSearchKey(req.Key)
+}
+
+func (req MessageTextSearchQuery) GetMethod() domain.SearchMethod {
+ return req.Method
+}
+
+func (req MessageTextSearchQuery) GetValue() interface{} {
+ return req.Value
+}
+
+func (key MessageTextSearchKey) ToColumnName() string {
+ switch iam_model.MessageTextSearchKey(key) {
+ case iam_model.MessageTextSearchKeyAggregateID:
+ return MessageTextKeyAggregateID
+ case iam_model.MessageTextSearchKeyMessageTextType:
+ return MessageTextKeyMessageTextType
+ case iam_model.MessageTextSearchKeyLanguage:
+ return MessageTextKeyLanguage
+ default:
+ return ""
+ }
+}
diff --git a/internal/management/repository/eventsourcing/eventstore/org.go b/internal/management/repository/eventsourcing/eventstore/org.go
index 852be5df17..cfc26963e5 100644
--- a/internal/management/repository/eventsourcing/eventstore/org.go
+++ b/internal/management/repository/eventsourcing/eventstore/org.go
@@ -548,19 +548,19 @@ func (repo *OrgRepository) GetMailTemplate(ctx context.Context) (*iam_model.Mail
return iam_es_model.MailTemplateViewToModel(template), err
}
-func (repo *OrgRepository) GetDefaultMailTexts(ctx context.Context) (*iam_model.MailTextsView, error) {
- texts, err := repo.View.MailTextsByAggregateID(repo.SystemDefaults.IamID)
+func (repo *OrgRepository) GetDefaultMessageTexts(ctx context.Context) (*iam_model.MessageTextsView, error) {
+ texts, err := repo.View.MessageTextsByAggregateID(repo.SystemDefaults.IamID)
if err != nil {
return nil, err
}
- return iam_es_model.MailTextsViewToModel(texts, true), err
+ return iam_es_model.MessageTextsViewToModel(texts, true), err
}
-func (repo *OrgRepository) GetMailTexts(ctx context.Context) (*iam_model.MailTextsView, error) {
+func (repo *OrgRepository) GetMessageTexts(ctx context.Context) (*iam_model.MessageTextsView, error) {
defaultIn := false
- texts, err := repo.View.MailTextsByAggregateID(authz.GetCtxData(ctx).OrgID)
+ texts, err := repo.View.MessageTextsByAggregateID(authz.GetCtxData(ctx).OrgID)
if errors.IsNotFound(err) || len(texts) == 0 {
- texts, err = repo.View.MailTextsByAggregateID(repo.SystemDefaults.IamID)
+ texts, err = repo.View.MessageTextsByAggregateID(repo.SystemDefaults.IamID)
if err != nil {
return nil, err
}
@@ -569,7 +569,31 @@ func (repo *OrgRepository) GetMailTexts(ctx context.Context) (*iam_model.MailTex
if err != nil {
return nil, err
}
- return iam_es_model.MailTextsViewToModel(texts, defaultIn), err
+ return iam_es_model.MessageTextsViewToModel(texts, defaultIn), err
+}
+
+func (repo *OrgRepository) GetDefaultMessageText(ctx context.Context, textType, lang string) (*iam_model.MessageTextView, error) {
+ text, err := repo.View.MessageTextByIDs(repo.SystemDefaults.IamID, textType, lang)
+ if err != nil {
+ return nil, err
+ }
+ text.Default = true
+ return iam_es_model.MessageTextViewToModel(text), err
+}
+
+func (repo *OrgRepository) GetMessageText(ctx context.Context, orgID, textType, lang string) (*iam_model.MessageTextView, error) {
+ text, err := repo.View.MessageTextByIDs(orgID, textType, lang)
+ if errors.IsNotFound(err) {
+ result, err := repo.GetDefaultMessageText(ctx, textType, lang)
+ if err != nil {
+ return nil, err
+ }
+ return result, nil
+ }
+ if err != nil {
+ return nil, err
+ }
+ return iam_es_model.MessageTextViewToModel(text), err
}
func (repo *OrgRepository) getOrgChanges(ctx context.Context, orgID string, lastSequence uint64, limit uint64, sortAscending bool, auditLogRetention time.Duration) (*org_model.OrgChanges, error) {
diff --git a/internal/management/repository/eventsourcing/handler/handler.go b/internal/management/repository/eventsourcing/handler/handler.go
index 1f063b2aa2..1610db3d36 100644
--- a/internal/management/repository/eventsourcing/handler/handler.go
+++ b/internal/management/repository/eventsourcing/handler/handler.go
@@ -77,8 +77,8 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
handler{view, bulkLimit, configs.cycleDuration("OrgIAMPolicy"), errorCount, es}),
newMailTemplate(
handler{view, bulkLimit, configs.cycleDuration("MailTemplate"), errorCount, es}),
- newMailText(
- handler{view, bulkLimit, configs.cycleDuration("MailText"), errorCount, es}),
+ newMessageText(
+ handler{view, bulkLimit, configs.cycleDuration("MessageText"), errorCount, es}),
newFeatures(
handler{view, bulkLimit, configs.cycleDuration("Features"), errorCount, es}),
}
diff --git a/internal/management/repository/eventsourcing/handler/mail_text.go b/internal/management/repository/eventsourcing/handler/mail_text.go
deleted file mode 100644
index c7ac8418d2..0000000000
--- a/internal/management/repository/eventsourcing/handler/mail_text.go
+++ /dev/null
@@ -1,111 +0,0 @@
-package handler
-
-import (
- "github.com/caos/logging"
- "github.com/caos/zitadel/internal/eventstore/v1"
- es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
- "github.com/caos/zitadel/internal/eventstore/v1/query"
- "github.com/caos/zitadel/internal/eventstore/v1/spooler"
- iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
- iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
- "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
-)
-
-type MailText struct {
- handler
- subscription *v1.Subscription
-}
-
-func newMailText(handler handler) *MailText {
- h := &MailText{
- handler: handler,
- }
-
- h.subscribe()
-
- return h
-}
-
-func (m *MailText) subscribe() {
- m.subscription = m.es.Subscribe(m.AggregateTypes()...)
- go func() {
- for event := range m.subscription.Events {
- query.ReduceEvent(m, event)
- }
- }()
-}
-
-const (
- mailTextTable = "management.mail_texts"
-)
-
-func (m *MailText) ViewModel() string {
- return mailTextTable
-}
-
-func (_ *MailText) AggregateTypes() []es_models.AggregateType {
- return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate}
-}
-
-func (p *MailText) CurrentSequence() (uint64, error) {
- sequence, err := p.view.GetLatestMailTextSequence()
- if err != nil {
- return 0, err
- }
- return sequence.CurrentSequence, nil
-}
-
-func (m *MailText) EventQuery() (*es_models.SearchQuery, error) {
- sequence, err := m.view.GetLatestMailTextSequence()
- if err != nil {
- return nil, err
- }
- return es_models.NewSearchQuery().
- AggregateTypeFilter(m.AggregateTypes()...).
- LatestSequenceFilter(sequence.CurrentSequence), nil
-}
-
-func (m *MailText) Reduce(event *es_models.Event) (err error) {
- switch event.AggregateType {
- case model.OrgAggregate, iam_es_model.IAMAggregate:
- err = m.processMailText(event)
- }
- return err
-}
-
-func (m *MailText) processMailText(event *es_models.Event) (err error) {
- text := new(iam_model.MailTextView)
- switch event.Type {
- case iam_es_model.MailTextAdded, model.MailTextAdded:
- err = text.AppendEvent(event)
- case iam_es_model.MailTextChanged, model.MailTextChanged:
- err = text.SetData(event)
- if err != nil {
- return err
- }
- text, err = m.view.MailTextByIDs(event.AggregateID, text.MailTextType, text.Language)
- if err != nil {
- return err
- }
- text.ChangeDate = event.CreationDate
- err = text.AppendEvent(event)
- case model.MailTextRemoved:
- err = text.SetData(event)
- return m.view.DeleteMailText(event.AggregateID, text.MailTextType, text.Language, event)
- default:
- return m.view.ProcessedMailTextSequence(event)
- }
- if err != nil {
- return err
- }
- return m.view.PutMailText(text, event)
-}
-
-func (m *MailText) OnError(event *es_models.Event, err error) error {
- logging.LogWithFields("SPOOL-4Djo9", "id", event.AggregateID).WithError(err).Warn("something went wrong in label text handler")
- return spooler.HandleError(event, err, m.view.GetLatestMailTextFailedEvent, m.view.ProcessedMailTextFailedEvent, m.view.ProcessedMailTextSequence, m.errorCountUntilSkip)
-}
-
-func (o *MailText) OnSuccess() error {
- return spooler.HandleSuccess(o.view.UpdateMailTextSpoolerRunTimestamp)
-}
diff --git a/internal/management/repository/eventsourcing/handler/message_text.go b/internal/management/repository/eventsourcing/handler/message_text.go
new file mode 100644
index 0000000000..4943514484
--- /dev/null
+++ b/internal/management/repository/eventsourcing/handler/message_text.go
@@ -0,0 +1,122 @@
+package handler
+
+import (
+ "github.com/caos/logging"
+ caos_errs "github.com/caos/zitadel/internal/errors"
+ "github.com/caos/zitadel/internal/eventstore/v1"
+ es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
+ "github.com/caos/zitadel/internal/eventstore/v1/query"
+ "github.com/caos/zitadel/internal/eventstore/v1/spooler"
+ iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
+ iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
+ "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
+)
+
+type MessageText struct {
+ handler
+ subscription *v1.Subscription
+}
+
+func newMessageText(handler handler) *MessageText {
+ h := &MessageText{
+ handler: handler,
+ }
+
+ h.subscribe()
+
+ return h
+}
+
+func (m *MessageText) subscribe() {
+ m.subscription = m.es.Subscribe(m.AggregateTypes()...)
+ go func() {
+ for event := range m.subscription.Events {
+ query.ReduceEvent(m, event)
+ }
+ }()
+}
+
+const (
+ messageTextTable = "management.message_texts"
+)
+
+func (m *MessageText) ViewModel() string {
+ return messageTextTable
+}
+
+func (_ *MessageText) AggregateTypes() []es_models.AggregateType {
+ return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate}
+}
+
+func (p *MessageText) CurrentSequence() (uint64, error) {
+ sequence, err := p.view.GetLatestMessageTextSequence()
+ if err != nil {
+ return 0, err
+ }
+ return sequence.CurrentSequence, nil
+}
+
+func (m *MessageText) EventQuery() (*es_models.SearchQuery, error) {
+ sequence, err := m.view.GetLatestMessageTextSequence()
+ if err != nil {
+ return nil, err
+ }
+ return es_models.NewSearchQuery().
+ AggregateTypeFilter(m.AggregateTypes()...).
+ LatestSequenceFilter(sequence.CurrentSequence), nil
+}
+
+func (m *MessageText) Reduce(event *es_models.Event) (err error) {
+ switch event.AggregateType {
+ case model.OrgAggregate, iam_es_model.IAMAggregate:
+ err = m.processMessageText(event)
+ }
+ return err
+}
+
+func (m *MessageText) processMessageText(event *es_models.Event) (err error) {
+ message := new(iam_model.MessageTextView)
+ switch event.Type {
+ case iam_es_model.CustomTextSet, model.CustomTextSet,
+ iam_es_model.CustomTextRemoved, model.CustomTextRemoved:
+ text := new(iam_model.CustomText)
+ err = text.SetData(event)
+ if err != nil {
+ return err
+ }
+ message, err = m.view.MessageTextByIDs(event.AggregateID, text.Template, text.Language.String())
+ if err != nil && !caos_errs.IsNotFound(err) {
+ return err
+ }
+ if caos_errs.IsNotFound(err) {
+ err = nil
+ message = new(iam_model.MessageTextView)
+ message.Language = text.Language.String()
+ message.MessageTextType = text.Template
+ message.CreationDate = event.CreationDate
+ }
+ err = message.AppendEvent(event)
+ case model.CustomTextMessageRemoved:
+ text := new(iam_model.CustomText)
+ err = text.SetData(event)
+ if err != nil {
+ return err
+ }
+ return m.view.DeleteMessageText(event.AggregateID, text.Template, text.Language.String(), event)
+ default:
+ return m.view.ProcessedMessageTextSequence(event)
+ }
+ if err != nil {
+ return err
+ }
+ return m.view.PutMessageText(message, event)
+}
+
+func (m *MessageText) OnError(event *es_models.Event, err error) error {
+ logging.LogWithFields("SPOOL-4Djo9", "id", event.AggregateID).WithError(err).Warn("something went wrong in label text handler")
+ return spooler.HandleError(event, err, m.view.GetLatestMessageTextFailedEvent, m.view.ProcessedMessageTextFailedEvent, m.view.ProcessedMessageTextSequence, m.errorCountUntilSkip)
+}
+
+func (o *MessageText) OnSuccess() error {
+ return spooler.HandleSuccess(o.view.UpdateMessageTextSpoolerRunTimestamp)
+}
diff --git a/internal/management/repository/eventsourcing/view/mail_texts.go b/internal/management/repository/eventsourcing/view/mail_texts.go
deleted file mode 100644
index 7cae1a41d3..0000000000
--- a/internal/management/repository/eventsourcing/view/mail_texts.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package view
-
-import (
- "github.com/caos/zitadel/internal/errors"
- "github.com/caos/zitadel/internal/eventstore/v1/models"
- "github.com/caos/zitadel/internal/iam/repository/view"
- "github.com/caos/zitadel/internal/iam/repository/view/model"
- global_view "github.com/caos/zitadel/internal/view/repository"
-)
-
-const (
- mailTextTable = "management.mail_texts"
-)
-
-func (v *View) MailTextsByAggregateID(aggregateID string) ([]*model.MailTextView, error) {
- return view.GetMailTexts(v.Db, mailTextTable, aggregateID)
-}
-
-func (v *View) MailTextByIDs(aggregateID string, textType string, language string) (*model.MailTextView, error) {
- return view.GetMailTextByIDs(v.Db, mailTextTable, aggregateID, textType, language)
-}
-
-func (v *View) PutMailText(template *model.MailTextView, event *models.Event) error {
- err := view.PutMailText(v.Db, mailTextTable, template)
- if err != nil {
- return err
- }
- return v.ProcessedMailTextSequence(event)
-}
-
-func (v *View) DeleteMailText(aggregateID string, textType string, language string, event *models.Event) error {
- err := view.DeleteMailText(v.Db, mailTextTable, aggregateID, textType, language)
- if err != nil && !errors.IsNotFound(err) {
- return err
- }
- return v.ProcessedMailTextSequence(event)
-}
-
-func (v *View) GetLatestMailTextSequence() (*global_view.CurrentSequence, error) {
- return v.latestSequence(mailTextTable)
-}
-
-func (v *View) ProcessedMailTextSequence(event *models.Event) error {
- return v.saveCurrentSequence(mailTextTable, event)
-}
-
-func (v *View) UpdateMailTextSpoolerRunTimestamp() error {
- return v.updateSpoolerRunSequence(mailTextTable)
-}
-
-func (v *View) GetLatestMailTextFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
- return v.latestFailedEvent(mailTextTable, sequence)
-}
-
-func (v *View) ProcessedMailTextFailedEvent(failedEvent *global_view.FailedEvent) error {
- return v.saveFailedEvent(failedEvent)
-}
diff --git a/internal/management/repository/eventsourcing/view/message_texts.go b/internal/management/repository/eventsourcing/view/message_texts.go
new file mode 100644
index 0000000000..05345936a6
--- /dev/null
+++ b/internal/management/repository/eventsourcing/view/message_texts.go
@@ -0,0 +1,57 @@
+package view
+
+import (
+ "github.com/caos/zitadel/internal/errors"
+ "github.com/caos/zitadel/internal/eventstore/v1/models"
+ "github.com/caos/zitadel/internal/iam/repository/view"
+ "github.com/caos/zitadel/internal/iam/repository/view/model"
+ global_view "github.com/caos/zitadel/internal/view/repository"
+)
+
+const (
+ messageTextTable = "management.message_texts"
+)
+
+func (v *View) MessageTextsByAggregateID(aggregateID string) ([]*model.MessageTextView, error) {
+ return view.GetMessageTexts(v.Db, messageTextTable, aggregateID)
+}
+
+func (v *View) MessageTextByIDs(aggregateID, textType, lang string) (*model.MessageTextView, error) {
+ return view.GetMessageTextByIDs(v.Db, messageTextTable, aggregateID, textType, lang)
+}
+
+func (v *View) PutMessageText(template *model.MessageTextView, event *models.Event) error {
+ err := view.PutMessageText(v.Db, messageTextTable, template)
+ if err != nil {
+ return err
+ }
+ return v.ProcessedMessageTextSequence(event)
+}
+
+func (v *View) DeleteMessageText(aggregateID, textType, lang string, event *models.Event) error {
+ err := view.DeleteMessageText(v.Db, messageTextTable, aggregateID, textType, lang)
+ if err != nil && !errors.IsNotFound(err) {
+ return err
+ }
+ return v.ProcessedMessageTextSequence(event)
+}
+
+func (v *View) GetLatestMessageTextSequence() (*global_view.CurrentSequence, error) {
+ return v.latestSequence(messageTextTable)
+}
+
+func (v *View) ProcessedMessageTextSequence(event *models.Event) error {
+ return v.saveCurrentSequence(messageTextTable, event)
+}
+
+func (v *View) UpdateMessageTextSpoolerRunTimestamp() error {
+ return v.updateSpoolerRunSequence(messageTextTable)
+}
+
+func (v *View) GetLatestMessageTextFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
+ return v.latestFailedEvent(messageTextTable, sequence)
+}
+
+func (v *View) ProcessedMessageTextFailedEvent(failedEvent *global_view.FailedEvent) error {
+ return v.saveFailedEvent(failedEvent)
+}
diff --git a/internal/management/repository/org.go b/internal/management/repository/org.go
index bae912a8af..a0e3c5a22a 100644
--- a/internal/management/repository/org.go
+++ b/internal/management/repository/org.go
@@ -44,8 +44,10 @@ type OrgRepository interface {
GetDefaultMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error)
GetMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error)
- GetDefaultMailTexts(ctx context.Context) (*iam_model.MailTextsView, error)
- GetMailTexts(ctx context.Context) (*iam_model.MailTextsView, error)
+ GetDefaultMessageTexts(ctx context.Context) (*iam_model.MessageTextsView, error)
+ GetMessageTexts(ctx context.Context) (*iam_model.MessageTextsView, error)
+ GetDefaultMessageText(ctx context.Context, textType string, language string) (*iam_model.MessageTextView, error)
+ GetMessageText(ctx context.Context, orgID, textType, language string) (*iam_model.MessageTextView, error)
GetLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error)
GetPreviewLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error)
diff --git a/internal/notification/repository/eventsourcing/handler/notification.go b/internal/notification/repository/eventsourcing/handler/notification.go
index f42312226f..9802bd7e2c 100644
--- a/internal/notification/repository/eventsourcing/handler/notification.go
+++ b/internal/notification/repository/eventsourcing/handler/notification.go
@@ -29,19 +29,19 @@ import (
)
const (
- notificationTable = "notification.notifications"
- NotifyUserID = "NOTIFICATION"
- labelPolicyTableOrg = "management.label_policies"
- labelPolicyTableDef = "adminapi.label_policies"
- mailTemplateTableOrg = "management.mail_templates"
- mailTemplateTableDef = "adminapi.mail_templates"
- mailTextTableOrg = "management.mail_texts"
- mailTextTableDef = "adminapi.mail_texts"
- mailTextTypeDomainClaimed = "DomainClaimed"
- mailTextTypeInitCode = "InitCode"
- mailTextTypePasswordReset = "PasswordReset"
- mailTextTypeVerifyEmail = "VerifyEmail"
- mailTextTypeVerifyPhone = "VerifyPhone"
+ notificationTable = "notification.notifications"
+ NotifyUserID = "NOTIFICATION"
+ labelPolicyTableOrg = "management.label_policies"
+ labelPolicyTableDef = "adminapi.label_policies"
+ mailTemplateTableOrg = "management.mail_templates"
+ mailTemplateTableDef = "adminapi.mail_templates"
+ messageTextTableOrg = "management.message_texts"
+ messageTextTableDef = "adminapi.message_texts"
+ messageTextTypeDomainClaimed = "DomainClaimed"
+ messageTextTypeInitCode = "InitCode"
+ messageTextTypePasswordReset = "PasswordReset"
+ messageTextTypeVerifyEmail = "VerifyEmail"
+ messageTextTypeVerifyPhone = "VerifyPhone"
)
type Notification struct {
@@ -146,7 +146,6 @@ func (n *Notification) handleInitUserCode(event *models.Event) (err error) {
if err != nil || alreadyHandled {
return err
}
-
ctx := getSetNotifyContextData(event.ResourceOwner)
colors, err := n.getLabelPolicy(ctx)
if err != nil {
@@ -163,7 +162,7 @@ func (n *Notification) handleInitUserCode(event *models.Event) (err error) {
return err
}
- text, err := n.getMailText(ctx, mailTextTypeInitCode, user.PreferredLanguage)
+ text, err := n.getMessageText(user, messageTextTypeInitCode, user.PreferredLanguage)
if err != nil {
return err
}
@@ -202,7 +201,7 @@ func (n *Notification) handlePasswordCode(event *models.Event) (err error) {
return err
}
- text, err := n.getMailText(ctx, mailTextTypePasswordReset, user.PreferredLanguage)
+ text, err := n.getMessageText(user, messageTextTypePasswordReset, user.PreferredLanguage)
if err != nil {
return err
}
@@ -224,7 +223,6 @@ func (n *Notification) handleEmailVerificationCode(event *models.Event) (err err
if err != nil || alreadyHandled {
return nil
}
-
ctx := getSetNotifyContextData(event.ResourceOwner)
colors, err := n.getLabelPolicy(ctx)
if err != nil {
@@ -241,7 +239,7 @@ func (n *Notification) handleEmailVerificationCode(event *models.Event) (err err
return err
}
- text, err := n.getMailText(ctx, mailTextTypeVerifyEmail, user.PreferredLanguage)
+ text, err := n.getMessageText(user, messageTextTypeVerifyEmail, user.PreferredLanguage)
if err != nil {
return err
}
@@ -268,7 +266,11 @@ func (n *Notification) handlePhoneVerificationCode(event *models.Event) (err err
if err != nil {
return err
}
- err = types.SendPhoneVerificationCode(n.i18n, user, phoneCode, n.systemDefaults, n.AesCrypto)
+ text, err := n.getMessageText(user, messageTextTypeVerifyPhone, user.PreferredLanguage)
+ if err != nil {
+ return err
+ }
+ err = types.SendPhoneVerificationCode(text, user, phoneCode, n.systemDefaults, n.AesCrypto)
if err != nil {
return err
}
@@ -303,7 +305,7 @@ func (n *Notification) handleDomainClaimed(event *models.Event) (err error) {
return err
}
- text, err := n.getMailText(ctx, mailTextTypeDomainClaimed, user.PreferredLanguage)
+ text, err := n.getMessageText(user, messageTextTypeDomainClaimed, user.PreferredLanguage)
if err != nil {
return err
}
@@ -395,26 +397,29 @@ func (n *Notification) getMailTemplate(ctx context.Context) (*iam_model.MailTemp
}
// Read organization specific texts
-func (n *Notification) getMailText(ctx context.Context, textType string, lang string) (*iam_model.MailTextView, error) {
+func (n *Notification) getMessageText(user *model.NotifyUser, textType, lang string) (*iam_model.MessageTextView, error) {
langTag := language.Make(lang)
if langTag == language.Und {
- langTag = n.systemDefaults.DefaultLanguage
+ langTag = language.English
}
- base, _ := langTag.Base()
+ langBase, _ := langTag.Base()
+
+ defaultMessageText, err := n.view.MessageTextByIDs(n.systemDefaults.IamID, textType, langBase.String(), messageTextTableDef)
+ if err != nil {
+ return nil, err
+ }
+ defaultMessageText.Default = true
+
// read from Org
- mailText, err := n.view.MailTextByIDs(authz.GetCtxData(ctx).OrgID, textType, base.String(), mailTextTableOrg)
+ orgMessageText, err := n.view.MessageTextByIDs(user.ResourceOwner, textType, langBase.String(), messageTextTableOrg)
if errors.IsNotFound(err) {
- // read from default
- mailText, err = n.view.MailTextByIDs(n.systemDefaults.IamID, textType, base.String(), mailTextTableDef)
- if err != nil {
- return nil, err
- }
- mailText.Default = true
+ return iam_es_model.MessageTextViewToModel(defaultMessageText), nil
}
if err != nil {
return nil, err
}
- return iam_es_model.MailTextViewToModel(mailText), err
+ mergedText := mergeMessageTexts(defaultMessageText, orgMessageText)
+ return iam_es_model.MessageTextViewToModel(mergedText), err
}
func (n *Notification) getUserByID(userID string) (*model.NotifyUser, error) {
@@ -440,3 +445,28 @@ func (n *Notification) getUserByID(userID string) (*model.NotifyUser, error) {
}
return &userCopy, nil
}
+
+func mergeMessageTexts(defaultText *iam_es_model.MessageTextView, orgText *iam_es_model.MessageTextView) *iam_es_model.MessageTextView {
+ if orgText.Subject == "" {
+ orgText.Subject = defaultText.Subject
+ }
+ if orgText.Title == "" {
+ orgText.Title = defaultText.Title
+ }
+ if orgText.PreHeader == "" {
+ orgText.PreHeader = defaultText.PreHeader
+ }
+ if orgText.Text == "" {
+ orgText.Text = defaultText.Text
+ }
+ if orgText.Greeting == "" {
+ orgText.Greeting = defaultText.Greeting
+ }
+ if orgText.ButtonText == "" {
+ orgText.ButtonText = defaultText.ButtonText
+ }
+ if orgText.FooterText == "" {
+ orgText.FooterText = defaultText.FooterText
+ }
+ return orgText
+}
diff --git a/internal/notification/repository/eventsourcing/view/mail_text.go b/internal/notification/repository/eventsourcing/view/mail_text.go
deleted file mode 100644
index 3413b73455..0000000000
--- a/internal/notification/repository/eventsourcing/view/mail_text.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package view
-
-import (
- "github.com/caos/zitadel/internal/iam/repository/view"
- "github.com/caos/zitadel/internal/iam/repository/view/model"
-)
-
-func (v *View) MailTextByIDs(aggregateID string, textType string, language string, mailTextTableVar string) (*model.MailTextView, error) {
- return view.GetMailTextByIDs(v.Db, mailTextTableVar, aggregateID, textType, language)
-}
diff --git a/internal/notification/repository/eventsourcing/view/message_text.go b/internal/notification/repository/eventsourcing/view/message_text.go
new file mode 100644
index 0000000000..486895a475
--- /dev/null
+++ b/internal/notification/repository/eventsourcing/view/message_text.go
@@ -0,0 +1,10 @@
+package view
+
+import (
+ "github.com/caos/zitadel/internal/iam/repository/view"
+ "github.com/caos/zitadel/internal/iam/repository/view/model"
+)
+
+func (v *View) MessageTextByIDs(aggregateID, textType, lang, messageTextTableVar string) (*model.MessageTextView, error) {
+ return view.GetMessageTextByIDs(v.Db, messageTextTableVar, aggregateID, textType, lang)
+}
diff --git a/internal/notification/templates/templateData.go b/internal/notification/templates/templateData.go
index 2b7f119740..3197b52c3b 100644
--- a/internal/notification/templates/templateData.go
+++ b/internal/notification/templates/templateData.go
@@ -53,7 +53,7 @@ func (data *TemplateData) Translate(i18n *i18n.Translator, args map[string]inter
data.ButtonText = i18n.Localize(data.ButtonText, nil, langs...)
}
-func GetTemplateData(apiDomain, href string, text *iam_model.MailTextView, policy *iam_model.LabelPolicyView) TemplateData {
+func GetTemplateData(apiDomain, href string, text *iam_model.MessageTextView, policy *iam_model.LabelPolicyView) TemplateData {
templateData := TemplateData{
Title: text.Title,
PreHeader: text.PreHeader,
@@ -62,6 +62,7 @@ func GetTemplateData(apiDomain, href string, text *iam_model.MailTextView, polic
Text: html.UnescapeString(text.Text),
Href: href,
ButtonText: text.ButtonText,
+ FooterText: text.FooterText,
PrimaryColor: defaultPrimaryColor,
BackgroundColor: defaultBackgroundColor,
FontColor: defaultFontColor,
diff --git a/internal/notification/types/domain_claimed.go b/internal/notification/types/domain_claimed.go
index 0717f0ea70..58461125a6 100644
--- a/internal/notification/types/domain_claimed.go
+++ b/internal/notification/types/domain_claimed.go
@@ -15,18 +15,14 @@ type DomainClaimedData struct {
URL string
}
-func SendDomainClaimed(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, username string, systemDefaults systemdefaults.SystemDefaults, colors *iam_model.LabelPolicyView, apiDomain string) error {
+func SendDomainClaimed(mailhtml string, text *iam_model.MessageTextView, user *view_model.NotifyUser, username string, systemDefaults systemdefaults.SystemDefaults, colors *iam_model.LabelPolicyView, apiDomain string) error {
url, err := templates.ParseTemplateText(systemDefaults.Notifications.Endpoints.DomainClaimed, &UrlData{UserID: user.ID})
if err != nil {
return err
}
- var args = map[string]interface{}{
- "FirstName": user.FirstName,
- "LastName": user.LastName,
- "Username": user.LastEmail,
- "TempUsername": username,
- "Domain": strings.Split(user.LastEmail, "@")[1],
- }
+ var args = mapNotifyUserToArgs(user)
+ args["TempUsername"] = username
+ args["Domain"] = strings.Split(user.LastEmail, "@")[1]
text.Greeting, err = templates.ParseTemplateText(text.Greeting, args)
text.Text, err = templates.ParseTemplateText(text.Text, args)
diff --git a/internal/notification/types/email_verification_code.go b/internal/notification/types/email_verification_code.go
index 2654b4e897..4b88f17a91 100644
--- a/internal/notification/types/email_verification_code.go
+++ b/internal/notification/types/email_verification_code.go
@@ -16,7 +16,7 @@ type EmailVerificationCodeData struct {
URL string
}
-func SendEmailVerificationCode(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, code *es_model.EmailCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView, apiDomain string) error {
+func SendEmailVerificationCode(mailhtml string, text *iam_model.MessageTextView, user *view_model.NotifyUser, code *es_model.EmailCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView, apiDomain string) error {
codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil {
return err
@@ -25,11 +25,9 @@ func SendEmailVerificationCode(mailhtml string, text *iam_model.MailTextView, us
if err != nil {
return err
}
- var args = map[string]interface{}{
- "FirstName": user.FirstName,
- "LastName": user.LastName,
- "Code": codeString,
- }
+
+ var args = mapNotifyUserToArgs(user)
+ args["Code"] = codeString
text.Greeting, err = templates.ParseTemplateText(text.Greeting, args)
text.Text, err = templates.ParseTemplateText(text.Text, args)
diff --git a/internal/notification/types/init_code.go b/internal/notification/types/init_code.go
index 596af557d1..9fce037afe 100644
--- a/internal/notification/types/init_code.go
+++ b/internal/notification/types/init_code.go
@@ -22,7 +22,7 @@ type UrlData struct {
PasswordSet bool
}
-func SendUserInitCode(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, code *es_model.InitUserCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView, apiDomain string) error {
+func SendUserInitCode(mailhtml string, text *iam_model.MessageTextView, user *view_model.NotifyUser, code *es_model.InitUserCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView, apiDomain string) error {
codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil {
return err
@@ -31,12 +31,8 @@ func SendUserInitCode(mailhtml string, text *iam_model.MailTextView, user *view_
if err != nil {
return err
}
- var args = map[string]interface{}{
- "FirstName": user.FirstName,
- "LastName": user.LastName,
- "Code": codeString,
- "PreferredLoginName": user.PreferredLoginName,
- }
+ var args = mapNotifyUserToArgs(user)
+ args["Code"] = codeString
text.Greeting, err = templates.ParseTemplateText(text.Greeting, args)
text.Text, err = templates.ParseTemplateText(text.Text, args)
diff --git a/internal/notification/types/password_code.go b/internal/notification/types/password_code.go
index bdcb478dca..1eb1179362 100644
--- a/internal/notification/types/password_code.go
+++ b/internal/notification/types/password_code.go
@@ -18,7 +18,7 @@ type PasswordCodeData struct {
URL string
}
-func SendPasswordCode(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, code *es_model.PasswordCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView, apiDomain string) error {
+func SendPasswordCode(mailhtml string, text *iam_model.MessageTextView, user *view_model.NotifyUser, code *es_model.PasswordCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView, apiDomain string) error {
codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil {
return err
@@ -27,11 +27,8 @@ func SendPasswordCode(mailhtml string, text *iam_model.MailTextView, user *view_
if err != nil {
return err
}
- var args = map[string]interface{}{
- "FirstName": user.FirstName,
- "LastName": user.LastName,
- "Code": codeString,
- }
+ var args = mapNotifyUserToArgs(user)
+ args["Code"] = codeString
text.Greeting, err = templates.ParseTemplateText(text.Greeting, args)
text.Text, err = templates.ParseTemplateText(text.Text, args)
diff --git a/internal/notification/types/phone_verification_code.go b/internal/notification/types/phone_verification_code.go
index 0cb1eb472c..28447ce2e4 100644
--- a/internal/notification/types/phone_verification_code.go
+++ b/internal/notification/types/phone_verification_code.go
@@ -3,7 +3,7 @@ package types
import (
"github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto"
- "github.com/caos/zitadel/internal/i18n"
+ iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/notification/templates"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
view_model "github.com/caos/zitadel/internal/user/repository/view/model"
@@ -13,19 +13,18 @@ type PhoneVerificationCodeData struct {
UserID string
}
-func SendPhoneVerificationCode(i18n *i18n.Translator, user *view_model.NotifyUser, code *es_model.PhoneCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm) error {
+func SendPhoneVerificationCode(text *iam_model.MessageTextView, user *view_model.NotifyUser, code *es_model.PhoneCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm) error {
codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil {
return err
}
- var args = map[string]interface{}{
- "FirstName": user.FirstName,
- "LastName": user.LastName,
- "Code": codeString,
- }
- systemDefaults.Notifications.TemplateData.VerifyPhone.Translate(i18n, args, user.PreferredLanguage)
+ var args = mapNotifyUserToArgs(user)
+ args["Code"] = codeString
+
+ text.Text, err = templates.ParseTemplateText(text.Text, args)
+
codeData := &PhoneVerificationCodeData{UserID: user.ID}
- template, err := templates.ParseTemplateText(systemDefaults.Notifications.TemplateData.VerifyPhone.Text, codeData)
+ template, err := templates.ParseTemplateText(text.Text, codeData)
if err != nil {
return err
}
diff --git a/internal/notification/types/user_email.go b/internal/notification/types/user_email.go
index 974aabc60c..4dfe6c9ed0 100644
--- a/internal/notification/types/user_email.go
+++ b/internal/notification/types/user_email.go
@@ -42,3 +42,21 @@ func sendDebugEmail(message providers.Message, config systemdefaults.Notificatio
}
return provider.HandleMessage(message)
}
+
+func mapNotifyUserToArgs(user *view_model.NotifyUser) map[string]interface{} {
+ return map[string]interface{}{
+ "UserName": user.UserName,
+ "FirstName": user.FirstName,
+ "LastName": user.LastName,
+ "NickName": user.NickName,
+ "DisplayName": user.DisplayName,
+ "LastEmail": user.LastEmail,
+ "VerifiedEmail": user.VerifiedEmail,
+ "LastPhone": user.LastPhone,
+ "VerifiedPhone": user.VerifiedPhone,
+ "PreferredLoginName": user.PreferredLoginName,
+ "LoginNames": user.LoginNames,
+ "ChangeDate": user.ChangeDate,
+ "CreationDate": user.CreationDate,
+ }
+}
diff --git a/internal/org/repository/eventsourcing/model/mail_text.go b/internal/org/repository/eventsourcing/model/mail_text.go
deleted file mode 100644
index 908228b364..0000000000
--- a/internal/org/repository/eventsourcing/model/mail_text.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package model
-
-import (
- es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
- iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
-)
-
-func (o *Org) appendAddMailTextEvent(event *es_models.Event) error {
- mailText := &iam_es_model.MailText{}
- err := mailText.SetDataLabel(event)
- if err != nil {
- return err
- }
- mailText.ObjectRoot.CreationDate = event.CreationDate
- o.MailTexts = append(o.MailTexts, mailText)
- return nil
-}
-
-func (o *Org) appendChangeMailTextEvent(event *es_models.Event) error {
- mailText := &iam_es_model.MailText{}
- err := mailText.SetDataLabel(event)
- if err != nil {
- return err
- }
- mailText.ObjectRoot.ChangeDate = event.CreationDate
- if n, m := iam_es_model.GetMailText(o.MailTexts, mailText.MailTextType, mailText.Language); m != nil {
- o.MailTexts[n] = mailText
- }
- return nil
-}
-
-func (o *Org) appendRemoveMailTextEvent(event *es_models.Event) error {
- mailText := &iam_es_model.MailText{}
- err := mailText.SetDataLabel(event)
- if err != nil {
- return err
- }
- if n, m := iam_es_model.GetMailText(o.MailTexts, mailText.MailTextType, mailText.Language); m != nil {
- o.MailTexts[n] = o.MailTexts[len(o.MailTexts)-1]
- o.MailTexts[len(o.MailTexts)-1] = nil
- o.MailTexts = o.MailTexts[:len(o.MailTexts)-1]
- }
- return nil
-}
diff --git a/internal/org/repository/eventsourcing/model/mail_text_test.go b/internal/org/repository/eventsourcing/model/mail_text_test.go
deleted file mode 100644
index 49de9b3d6f..0000000000
--- a/internal/org/repository/eventsourcing/model/mail_text_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package model
-
-import (
- "encoding/json"
- "testing"
-
- es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
- iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
-)
-
-func TestAppendAddMailTextEvent(t *testing.T) {
- type args struct {
- org *Org
- mailText *iam_es_model.MailText
- event *es_models.Event
- }
- tests := []struct {
- name string
- args args
- result *Org
- }{
- {
- name: "append add mail text event",
- args: args{
- org: &Org{},
- mailText: &iam_es_model.MailText{MailTextType: "Type", Language: "DE"},
- event: &es_models.Event{},
- },
- result: &Org{MailTexts: []*iam_es_model.MailText{&iam_es_model.MailText{MailTextType: "Type", Language: "DE"}}},
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if tt.args.mailText != nil {
- data, _ := json.Marshal(tt.args.mailText)
- tt.args.event.Data = data
- }
- tt.args.org.appendAddMailTextEvent(tt.args.event)
- if len(tt.args.org.MailTexts) != 1 {
- t.Errorf("got wrong result should have one mailtext actual: %v ", len(tt.args.org.MailTexts))
- }
- if tt.result.MailTexts[0].Language != tt.args.org.MailTexts[0].Language {
- t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.MailTexts[0].Language, tt.args.org.MailTexts[0].Language)
- }
- })
- }
-}
-
-func TestAppendChangeMailTextEvent(t *testing.T) {
- type args struct {
- org *Org
- mailText *iam_es_model.MailText
- event *es_models.Event
- }
- tests := []struct {
- name string
- args args
- result *Org
- }{
- {
- name: "append change mail text event",
- args: args{
- org: &Org{MailTexts: []*iam_es_model.MailText{&iam_es_model.MailText{
- Language: "DE",
- MailTextType: "TypeX",
- }}},
- mailText: &iam_es_model.MailText{MailTextType: "Type", Language: "DE"},
- event: &es_models.Event{},
- },
- result: &Org{MailTexts: []*iam_es_model.MailText{&iam_es_model.MailText{
- Language: "DE",
- MailTextType: "Type",
- }}},
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if tt.args.mailText != nil {
- data, _ := json.Marshal(tt.args.mailText)
- tt.args.event.Data = data
- }
- tt.args.org.appendChangeMailTextEvent(tt.args.event)
- if len(tt.args.org.MailTexts) != 1 {
- t.Errorf("got wrong result should have one mailtext actual: %v ", len(tt.args.org.MailTexts))
- }
- if tt.result.MailTexts[0].Language != tt.args.org.MailTexts[0].Language {
- t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.MailTexts[0].Language, tt.args.org.MailTexts[0].Language)
- }
- })
- }
-}
diff --git a/internal/org/repository/eventsourcing/model/org.go b/internal/org/repository/eventsourcing/model/org.go
index 499e44a64e..422e6ac7e7 100644
--- a/internal/org/repository/eventsourcing/model/org.go
+++ b/internal/org/repository/eventsourcing/model/org.go
@@ -26,7 +26,6 @@ type Org struct {
OrgIAMPolicy *iam_es_model.OrgIAMPolicy `json:"-"`
LabelPolicy *iam_es_model.LabelPolicy `json:"-"`
MailTemplate *iam_es_model.MailTemplate `json:"-"`
- MailTexts []*iam_es_model.MailText `json:"-"`
IDPs []*iam_es_model.IDPConfig `json:"-"`
LoginPolicy *iam_es_model.LoginPolicy `json:"-"`
PasswordComplexityPolicy *iam_es_model.PasswordComplexityPolicy `json:"-"`
@@ -34,44 +33,6 @@ type Org struct {
PasswordLockoutPolicy *iam_es_model.PasswordLockoutPolicy `json:"-"`
}
-func OrgFromModel(org *org_model.Org) *Org {
- members := OrgMembersFromModel(org.Members)
- domains := OrgDomainsFromModel(org.Domains)
- idps := iam_es_model.IDPConfigsFromModel(org.IDPs)
- mailTexts := iam_es_model.MailTextsFromModel(org.MailTexts)
- converted := &Org{
- ObjectRoot: org.ObjectRoot,
- Name: org.Name,
- State: int32(org.State),
- Domains: domains,
- MailTexts: mailTexts,
- Members: members,
- IDPs: idps,
- }
- if org.OrgIamPolicy != nil {
- converted.OrgIAMPolicy = iam_es_model.OrgIAMPolicyFromModel(org.OrgIamPolicy)
- }
- if org.LoginPolicy != nil {
- converted.LoginPolicy = iam_es_model.LoginPolicyFromModel(org.LoginPolicy)
- }
- if org.LabelPolicy != nil {
- converted.LabelPolicy = iam_es_model.LabelPolicyFromModel(org.LabelPolicy)
- }
- if org.MailTemplate != nil {
- converted.MailTemplate = iam_es_model.MailTemplateFromModel(org.MailTemplate)
- }
- if org.PasswordComplexityPolicy != nil {
- converted.PasswordComplexityPolicy = iam_es_model.PasswordComplexityPolicyFromModel(org.PasswordComplexityPolicy)
- }
- if org.PasswordAgePolicy != nil {
- converted.PasswordAgePolicy = iam_es_model.PasswordAgePolicyFromModel(org.PasswordAgePolicy)
- }
- if org.PasswordLockoutPolicy != nil {
- converted.PasswordLockoutPolicy = iam_es_model.PasswordLockoutPolicyFromModel(org.PasswordLockoutPolicy)
- }
- return converted
-}
-
func OrgToModel(org *Org) *org_model.Org {
converted := &org_model.Org{
ObjectRoot: org.ObjectRoot,
@@ -79,7 +40,6 @@ func OrgToModel(org *Org) *org_model.Org {
State: org_model.OrgState(org.State),
Domains: OrgDomainsToModel(org.Domains),
Members: OrgMembersToModel(org.Members),
- MailTexts: iam_es_model.MailTextsToModel(org.MailTexts),
IDPs: iam_es_model.IDPConfigsToModel(org.IDPs),
}
if org.OrgIAMPolicy != nil {
@@ -216,12 +176,6 @@ func (o *Org) AppendEvent(event *es_models.Event) (err error) {
err = o.appendChangeMailTemplateEvent(event)
case MailTemplateRemoved:
o.appendRemoveMailTemplateEvent(event)
- case MailTextAdded:
- err = o.appendAddMailTextEvent(event)
- case MailTextChanged:
- err = o.appendChangeMailTextEvent(event)
- case MailTextRemoved:
- o.appendRemoveMailTextEvent(event)
case LoginPolicySecondFactorAdded:
err = o.appendAddSecondFactorToLoginPolicyEvent(event)
case LoginPolicySecondFactorRemoved:
diff --git a/internal/org/repository/eventsourcing/model/types.go b/internal/org/repository/eventsourcing/model/types.go
index 4a00b147da..17eda9952f 100644
--- a/internal/org/repository/eventsourcing/model/types.go
+++ b/internal/org/repository/eventsourcing/model/types.go
@@ -76,9 +76,10 @@ const (
MailTemplateAdded models.EventType = "org.mail.template.added"
MailTemplateChanged models.EventType = "org.mail.template.changed"
MailTemplateRemoved models.EventType = "org.mail.template.removed"
- MailTextAdded models.EventType = "org.mail.text.added"
- MailTextChanged models.EventType = "org.mail.text.changed"
- MailTextRemoved models.EventType = "org.mail.text.removed"
+
+ CustomTextSet models.EventType = "org.customtext.set"
+ CustomTextRemoved models.EventType = "org.customtext.removed"
+ CustomTextMessageRemoved models.EventType = "org.customtext.template.removed"
PasswordComplexityPolicyAdded models.EventType = "org.policy.password.complexity.added"
PasswordComplexityPolicyChanged models.EventType = "org.policy.password.complexity.changed"
diff --git a/internal/project/model/project_grant_view.go b/internal/project/model/project_grant_view.go
index 665f73d0b2..4ceb8b5225 100644
--- a/internal/project/model/project_grant_view.go
+++ b/internal/project/model/project_grant_view.go
@@ -81,7 +81,7 @@ func (r *ProjectGrantViewSearchRequest) AppendMyResourceOwnerQuery(orgID string)
func (r *ProjectGrantViewSearchRequest) EnsureLimit(limit uint64) error {
if r.Limit > limit {
- return caos_errors.ThrowInvalidArgument(nil, "SEARCH-2n8fS", "Errors.Limit.ExceedsDefault")
+ return caos_errors.ThrowInvalidArgument(nil, "SEARCH-0fj3s", "Errors.Limit.ExceedsDefault")
}
if r.Limit == 0 {
r.Limit = limit
diff --git a/internal/repository/features/features.go b/internal/repository/features/features.go
index b7dde676f4..d714209e0d 100644
--- a/internal/repository/features/features.go
+++ b/internal/repository/features/features.go
@@ -35,6 +35,7 @@ type FeaturesSetEvent struct {
LabelPolicyPrivateLabel *bool `json:"labelPolicyPrivateLabel,omitempty"`
LabelPolicyWatermark *bool `json:"labelPolicyWatermark,omitempty"`
CustomDomain *bool `json:"customDomain,omitempty"`
+ CustomText *bool `json:"customText,omitempty"`
}
func (e *FeaturesSetEvent) Data() interface{} {
@@ -153,6 +154,11 @@ func ChangeCustomDomain(customDomain bool) func(event *FeaturesSetEvent) {
}
}
+func ChangeCustomText(customText bool) func(event *FeaturesSetEvent) {
+ return func(e *FeaturesSetEvent) {
+ e.CustomText = &customText
+ }
+}
func FeaturesSetEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e := &FeaturesSetEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
diff --git a/internal/repository/iam/custom_text.go b/internal/repository/iam/custom_text.go
new file mode 100644
index 0000000000..0bec3d5b24
--- /dev/null
+++ b/internal/repository/iam/custom_text.go
@@ -0,0 +1,46 @@
+package iam
+
+import (
+ "context"
+
+ "golang.org/x/text/language"
+
+ "github.com/caos/zitadel/internal/eventstore"
+ "github.com/caos/zitadel/internal/eventstore/repository"
+ "github.com/caos/zitadel/internal/repository/policy"
+)
+
+var (
+ CustomTextSetEventType = iamEventTypePrefix + policy.CustomTextSetEventType
+)
+
+type CustomTextSetEvent struct {
+ policy.CustomTextSetEvent
+}
+
+func NewCustomTextSetEvent(
+ ctx context.Context,
+ aggregate *eventstore.Aggregate,
+ template,
+ key,
+ text string,
+ language language.Tag,
+) *CustomTextSetEvent {
+ return &CustomTextSetEvent{
+ CustomTextSetEvent: *policy.NewCustomTextSetEvent(
+ eventstore.NewBaseEventForPush(ctx, aggregate, CustomTextSetEventType),
+ template,
+ key,
+ text,
+ language),
+ }
+}
+
+func CustomTextSetEventMapper(event *repository.Event) (eventstore.EventReader, error) {
+ e, err := policy.CustomTextSetEventMapper(event)
+ if err != nil {
+ return nil, err
+ }
+
+ return &CustomTextSetEvent{CustomTextSetEvent: *e.(*policy.CustomTextSetEvent)}, nil
+}
diff --git a/internal/repository/iam/eventstore.go b/internal/repository/iam/eventstore.go
index 61b9d8a6ef..bcfc96f563 100644
--- a/internal/repository/iam/eventstore.go
+++ b/internal/repository/iam/eventstore.go
@@ -56,5 +56,6 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
RegisterFilterEventMapper(MailTemplateChangedEventType, MailTemplateChangedEventMapper).
RegisterFilterEventMapper(MailTextAddedEventType, MailTextAddedEventMapper).
RegisterFilterEventMapper(MailTextChangedEventType, MailTextChangedEventMapper).
+ RegisterFilterEventMapper(CustomTextSetEventType, CustomTextSetEventMapper).
RegisterFilterEventMapper(FeaturesSetEventType, FeaturesSetEventMapper)
}
diff --git a/internal/repository/org/custom_text.go b/internal/repository/org/custom_text.go
new file mode 100644
index 0000000000..11a7ceb104
--- /dev/null
+++ b/internal/repository/org/custom_text.go
@@ -0,0 +1,107 @@
+package org
+
+import (
+ "context"
+
+ "golang.org/x/text/language"
+
+ "github.com/caos/zitadel/internal/eventstore"
+
+ "github.com/caos/zitadel/internal/eventstore/repository"
+ "github.com/caos/zitadel/internal/repository/policy"
+)
+
+var (
+ CustomTextSetEventType = orgEventTypePrefix + policy.CustomTextSetEventType
+ CustomTextRemovedEventType = orgEventTypePrefix + policy.CustomTextRemovedEventType
+ CustomTextTemplateRemovedEventType = orgEventTypePrefix + policy.CustomTextTemplateRemovedEventType
+)
+
+type CustomTextSetEvent struct {
+ policy.CustomTextSetEvent
+}
+
+func NewCustomTextSetEvent(
+ ctx context.Context,
+ aggregate *eventstore.Aggregate,
+ template,
+ key,
+ text string,
+ language language.Tag,
+) *CustomTextSetEvent {
+ return &CustomTextSetEvent{
+ CustomTextSetEvent: *policy.NewCustomTextSetEvent(
+ eventstore.NewBaseEventForPush(ctx, aggregate, CustomTextSetEventType),
+ template,
+ key,
+ text,
+ language),
+ }
+}
+
+func CustomTextSetEventMapper(event *repository.Event) (eventstore.EventReader, error) {
+ e, err := policy.CustomTextSetEventMapper(event)
+ if err != nil {
+ return nil, err
+ }
+
+ return &CustomTextSetEvent{CustomTextSetEvent: *e.(*policy.CustomTextSetEvent)}, nil
+}
+
+type CustomTextRemovedEvent struct {
+ policy.CustomTextRemovedEvent
+}
+
+func NewCustomTextRemovedEvent(
+ ctx context.Context,
+ aggregate *eventstore.Aggregate,
+ template,
+ key string,
+ language language.Tag,
+) *CustomTextRemovedEvent {
+ return &CustomTextRemovedEvent{
+ CustomTextRemovedEvent: *policy.NewCustomTextRemovedEvent(
+ eventstore.NewBaseEventForPush(ctx, aggregate, CustomTextRemovedEventType),
+ template,
+ key,
+ language,
+ ),
+ }
+}
+
+func CustomTextRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
+ e, err := policy.CustomTextRemovedEventMapper(event)
+ if err != nil {
+ return nil, err
+ }
+
+ return &CustomTextRemovedEvent{CustomTextRemovedEvent: *e.(*policy.CustomTextRemovedEvent)}, nil
+}
+
+type CustomTextTemplateRemovedEvent struct {
+ policy.CustomTextTemplateRemovedEvent
+}
+
+func NewCustomTextTemplateRemovedEvent(
+ ctx context.Context,
+ aggregate *eventstore.Aggregate,
+ template string,
+ language language.Tag,
+) *CustomTextTemplateRemovedEvent {
+ return &CustomTextTemplateRemovedEvent{
+ CustomTextTemplateRemovedEvent: *policy.NewCustomTextTemplateRemovedEvent(
+ eventstore.NewBaseEventForPush(ctx, aggregate, CustomTextTemplateRemovedEventType),
+ template,
+ language,
+ ),
+ }
+}
+
+func CustomTextTemplateRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
+ e, err := policy.CustomTextTemplateRemovedEventMapper(event)
+ if err != nil {
+ return nil, err
+ }
+
+ return &CustomTextTemplateRemovedEvent{CustomTextTemplateRemovedEvent: *e.(*policy.CustomTextTemplateRemovedEvent)}, nil
+}
diff --git a/internal/repository/org/eventstore.go b/internal/repository/org/eventstore.go
index e5df74644a..7602ad9e58 100644
--- a/internal/repository/org/eventstore.go
+++ b/internal/repository/org/eventstore.go
@@ -62,6 +62,9 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
RegisterFilterEventMapper(MailTextAddedEventType, MailTextAddedEventMapper).
RegisterFilterEventMapper(MailTextChangedEventType, MailTextChangedEventMapper).
RegisterFilterEventMapper(MailTextRemovedEventType, MailTextRemovedEventMapper).
+ RegisterFilterEventMapper(CustomTextSetEventType, CustomTextSetEventMapper).
+ RegisterFilterEventMapper(CustomTextRemovedEventType, CustomTextRemovedEventMapper).
+ RegisterFilterEventMapper(CustomTextTemplateRemovedEventType, CustomTextTemplateRemovedEventMapper).
RegisterFilterEventMapper(IDPConfigAddedEventType, IDPConfigAddedEventMapper).
RegisterFilterEventMapper(IDPConfigChangedEventType, IDPConfigChangedEventMapper).
RegisterFilterEventMapper(IDPConfigRemovedEventType, IDPConfigRemovedEventMapper).
diff --git a/internal/repository/policy/custom_text.go b/internal/repository/policy/custom_text.go
new file mode 100644
index 0000000000..7ad6a5dc9c
--- /dev/null
+++ b/internal/repository/policy/custom_text.go
@@ -0,0 +1,138 @@
+package policy
+
+import (
+ "encoding/json"
+
+ "golang.org/x/text/language"
+
+ "github.com/caos/zitadel/internal/errors"
+ "github.com/caos/zitadel/internal/eventstore"
+ "github.com/caos/zitadel/internal/eventstore/repository"
+)
+
+const (
+ customTextPrefix = "customtext."
+ CustomTextSetEventType = customTextPrefix + "set"
+ CustomTextRemovedEventType = customTextPrefix + "removed"
+ CustomTextTemplateRemovedEventType = customTextPrefix + "template.removed"
+)
+
+type CustomTextSetEvent struct {
+ eventstore.BaseEvent `json:"-"`
+
+ Template string `json:"template,omitempty"`
+ Key string `json:"key,omitempty"`
+ Language language.Tag `json:"language,omitempty"`
+ Text string `json:"text,omitempty"`
+}
+
+func (e *CustomTextSetEvent) Data() interface{} {
+ return e
+}
+
+func (e *CustomTextSetEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
+ return nil
+}
+
+func NewCustomTextSetEvent(
+ base *eventstore.BaseEvent,
+ template,
+ key,
+ text string,
+ language language.Tag,
+) *CustomTextSetEvent {
+ return &CustomTextSetEvent{
+ BaseEvent: *base,
+ Template: template,
+ Key: key,
+ Language: language,
+ Text: text,
+ }
+}
+
+func CustomTextSetEventMapper(event *repository.Event) (eventstore.EventReader, error) {
+ e := &CustomTextSetEvent{
+ BaseEvent: *eventstore.BaseEventFromRepo(event),
+ }
+
+ err := json.Unmarshal(event.Data, e)
+ if err != nil {
+ return nil, errors.ThrowInternal(err, "TEXT-28dwe", "unable to unmarshal custom text")
+ }
+
+ return e, nil
+}
+
+type CustomTextRemovedEvent struct {
+ eventstore.BaseEvent `json:"-"`
+
+ Template string `json:"template,omitempty"`
+ Key string `json:"key,omitempty"`
+ Language language.Tag `json:"language,omitempty"`
+}
+
+func (e *CustomTextRemovedEvent) Data() interface{} {
+ return e
+}
+
+func (e *CustomTextRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
+ return nil
+}
+
+func NewCustomTextRemovedEvent(base *eventstore.BaseEvent, template, key string, language language.Tag) *CustomTextRemovedEvent {
+ return &CustomTextRemovedEvent{
+ BaseEvent: *base,
+ Template: template,
+ Key: key,
+ Language: language,
+ }
+}
+
+func CustomTextRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
+ e := &CustomTextRemovedEvent{
+ BaseEvent: *eventstore.BaseEventFromRepo(event),
+ }
+
+ err := json.Unmarshal(event.Data, e)
+ if err != nil {
+ return nil, errors.ThrowInternal(err, "TEXT-28sMf", "unable to unmarshal custom text removed")
+ }
+
+ return e, nil
+}
+
+type CustomTextTemplateRemovedEvent struct {
+ eventstore.BaseEvent `json:"-"`
+
+ Template string `json:"template,omitempty"`
+ Language language.Tag `json:"language,omitempty"`
+}
+
+func (e *CustomTextTemplateRemovedEvent) Data() interface{} {
+ return e
+}
+
+func (e *CustomTextTemplateRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
+ return nil
+}
+
+func NewCustomTextTemplateRemovedEvent(base *eventstore.BaseEvent, template string, language language.Tag) *CustomTextTemplateRemovedEvent {
+ return &CustomTextTemplateRemovedEvent{
+ BaseEvent: *base,
+ Template: template,
+ Language: language,
+ }
+}
+
+func CustomTextTemplateRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
+ e := &CustomTextTemplateRemovedEvent{
+ BaseEvent: *eventstore.BaseEventFromRepo(event),
+ }
+
+ err := json.Unmarshal(event.Data, e)
+ if err != nil {
+ return nil, errors.ThrowInternal(err, "TEXT-mKKRs", "unable to unmarshal custom text message removed")
+ }
+
+ return e, nil
+}
diff --git a/internal/setup/config.go b/internal/setup/config.go
index d6100b2abc..8f8b44ab21 100644
--- a/internal/setup/config.go
+++ b/internal/setup/config.go
@@ -21,6 +21,7 @@ type IAMSetUp struct {
Step13 *command.Step13
Step14 *command.Step14
Step15 *command.Step15
+ Step16 *command.Step16
}
func (setup *IAMSetUp) Steps(currentDone domain.Step) ([]command.Step, error) {
@@ -42,6 +43,7 @@ func (setup *IAMSetUp) Steps(currentDone domain.Step) ([]command.Step, error) {
setup.Step13,
setup.Step14,
setup.Step15,
+ setup.Step16,
} {
if step.Step() <= currentDone {
continue
diff --git a/internal/static/i18n/de.yaml b/internal/static/i18n/de.yaml
index 3e7e80d8f2..90a688535e 100644
--- a/internal/static/i18n/de.yaml
+++ b/internal/static/i18n/de.yaml
@@ -162,11 +162,11 @@ Errors:
NotChanged: Default Mail Template wurde nicht verändert
AlreadyExists: Default Mail Template existiert bereits
Invalid: Default Mail Template ist ungültig
- MailText:
- NotFound: Default Mail Text konnte nicht gefunden werden
- NotChanged: Default Mail Text wurde nicht verändert
- AlreadyExists: Default Mail Text existiert bereits
- Invalid: Default Mail Text ist ungültig
+ CustomMessageText:
+ NotFound: Default Message Text konnte nicht gefunden werden
+ NotChanged: Default Message Text wurde nicht verändert
+ AlreadyExists: Default Message Text existiert bereits
+ Invalid: Default Message Text ist ungültig
PasswordComplexity:
NotFound: Password Komplexitäts Policy konnte nicht gefunden werden
Empty: Passwort Komplexitäts Policy ist leer
@@ -276,11 +276,11 @@ Errors:
NotChanged: Default Mail Template wurde nicht verändert
AlreadyExists: Default Mail Template existiert bereits
Invalid: Default Mail Template ist ungültig
- MailText:
- NotFound: Default Mail Text konnte nicht gefunden werden
- NotChanged: Default Mail Text wurde nicht verändert
- AlreadyExists: Default Mail Text existiert bereits
- Invalid: Default Mail Text ist ungültig
+ CustomMessageText:
+ NotFound: Default Message Text konnte nicht gefunden werden
+ NotChanged: Default Message Text wurde nicht verändert
+ AlreadyExists: Default Message Text existiert bereits
+ Invalid: Default Message Text ist ungültig
PasswordComplexityPolicy:
NotFound: Default Password Complexity Policy konnte nicht gefunden werden
NotExisting: Default Password Complexity Policy existiert nicht
@@ -351,6 +351,10 @@ Errors:
AlreadyExists: Schritt ausgeführt existiert bereits
Features:
NotChanged: Feature hat nicht geändert
+ CustomText:
+ AlreadyExists: Kundenspezifischer Text existiert bereits
+ Invalid: Kundenspezifischer Text ist ungültig
+ NotFound: Kundenspezifischer Text nicht gefunden
EventTypes:
user:
added: Benutzer hinzugefügt
@@ -569,6 +573,11 @@ EventTypes:
config:
added: SAML IDP Konfiguration hinzugefügt
changed: SAML IDP Konfiguration geändert
+ customtext:
+ set: Kundenspezifischer Text wurde gesetzt
+ removed: Kundenspezifischer Text wurde entfernt
+ template:
+ removed: Kundenspezifisches Text Template wurde entfernt
policy:
login:
added: Login Richtlinie hinzugefügt
@@ -715,6 +724,9 @@ EventTypes:
config:
added: SAML IDP Konfiguration hinzugefügt
changed: SAML IDP Konfiguration geändert
+ customtext:
+ set: Text wurde gesetzt
+ removed: Text wurde entfernt
policy:
login:
added: Default Login Policy hinzugefügt
diff --git a/internal/static/i18n/en.yaml b/internal/static/i18n/en.yaml
index f9f9438810..41365e2bb1 100644
--- a/internal/static/i18n/en.yaml
+++ b/internal/static/i18n/en.yaml
@@ -162,11 +162,11 @@ Errors:
NotChanged: Default Mail Template has not been changed
AlreadyExists: Default Mail Template already exists
Invalid: Default Mail Template is invalid
- MailText:
- NotFound: Default Mail Text not found
- NotChanged: Default Mail Text has not been changed
- AlreadyExists: Default Mail Text already exists
- Invalid: Default Mail Text is invalid
+ CustomMessageText:
+ NotFound: Default Message Text not found
+ NotChanged: Default Message Text has not been changed
+ AlreadyExists: Default Message Text already exists
+ Invalid: Default Message Text is invalid
PasswordComplexity:
NotFound: Password Complexity Policy not found
Empty: Password Complexity Policy is empty
@@ -276,11 +276,11 @@ Errors:
NotChanged: Default Mail Template has not been changed
AlreadyExists: Default Mail Template already exists
Invalid: Default Mail Template is invalid
- MailText:
- NotFound: Default Mail Text not found
- NotChanged: Default Mail Text has not been changed
- AlreadyExists: Default Mail Text already exists
- Invalid: Default Mail Text is invalid
+ CustomMessageText:
+ NotFound: Default Message Text not found
+ NotChanged: Default Message Text has not been changed
+ AlreadyExists: Default Message Text already exists
+ Invalid: Default Message Text is invalid
PasswordComplexityPolicy:
NotFound: Default Private Label Policy not found
NotExisting: Default Password Complexity Policy not existing
@@ -351,6 +351,10 @@ Errors:
AlreadyExists: Step done already exists
Features:
NotChanged: Feature hat nicht geändert
+ CustomText:
+ AlreadyExists: Custom text already exists
+ Invalid: Custom text invalid
+ NotFound: Custom text not found
EventTypes:
user:
added: User added
diff --git a/internal/ui/login/handler/login_handler.go b/internal/ui/login/handler/login_handler.go
index baffebfb99..2915e356ac 100644
--- a/internal/ui/login/handler/login_handler.go
+++ b/internal/ui/login/handler/login_handler.go
@@ -42,7 +42,7 @@ func (l *Login) handleLoginNameCheck(w http.ResponseWriter, r *http.Request) {
data := new(loginData)
authReq, err := l.getAuthRequestAndParseData(r, data)
if err != nil {
- l.renderError(w, r, authReq, err)
+ l.renderLogin(w, r, authReq, err)
return
}
if data.Register {
diff --git a/internal/ui/login/static/i18n/de.yaml b/internal/ui/login/static/i18n/de.yaml
index b32ae723b2..22b4d2f2a6 100644
--- a/internal/ui/login/static/i18n/de.yaml
+++ b/internal/ui/login/static/i18n/de.yaml
@@ -235,6 +235,7 @@ Errors:
RequestTypeNotSupported: Requesttyp wird nicht unterstürzt
User:
NotFound: Benutzer konnte nicht gefunden werden
+ Inactive: Benutzer ist inaktiv
NotFoundOnOrg: Benutzer konnte in der gewünschten Organisation nicht gefunden werden
NotAllowedOrg: Benutzer gehört nicht der benötigten Organisation an
NotMatchingUserID: User stimm nicht mit User in Auth Request überein
diff --git a/internal/ui/login/static/i18n/en.yaml b/internal/ui/login/static/i18n/en.yaml
index 73df887929..3d1ad44c96 100644
--- a/internal/ui/login/static/i18n/en.yaml
+++ b/internal/ui/login/static/i18n/en.yaml
@@ -235,6 +235,7 @@ Errors:
RequestTypeNotSupported: Request type is not supported
User:
NotFound: User could not be found
+ Inactive: User is inactive
NotFoundOnOrg: User could not be found on chosen organisation
NotAllowedOrg: User is no member of the required organisation
NotMatchingUserID: User and user in authrequest don't match
diff --git a/migrations/cockroach/V1.48__custom_text.sql b/migrations/cockroach/V1.48__custom_text.sql
new file mode 100644
index 0000000000..756b638a88
--- /dev/null
+++ b/migrations/cockroach/V1.48__custom_text.sql
@@ -0,0 +1,53 @@
+ALTER TABLE adminapi.features ADD COLUMN custom_text BOOLEAN;
+ALTER TABLE auth.features ADD COLUMN custom_text BOOLEAN;
+ALTER TABLE authz.features ADD COLUMN custom_text BOOLEAN;
+ALTER TABLE management.features ADD COLUMN custom_text BOOLEAN;
+
+CREATE TABLE adminapi.message_texts (
+ aggregate_id TEXT,
+
+ creation_date TIMESTAMPTZ,
+ change_date TIMESTAMPTZ,
+ message_text_state SMALLINT,
+ sequence BIGINT,
+
+ message_text_type TEXT,
+ language TEXT,
+ title TEXT,
+ pre_header TEXT,
+ subject TEXT,
+ greeting TEXT,
+ text TEXT,
+ button_text TEXT,
+ footer_text TEXT,
+
+ PRIMARY KEY (aggregate_id, message_text_type, language)
+);
+
+
+CREATE TABLE management.message_texts (
+ aggregate_id TEXT,
+
+ creation_date TIMESTAMPTZ,
+ change_date TIMESTAMPTZ,
+ message_text_state SMALLINT,
+ sequence BIGINT,
+
+ message_text_type TEXT,
+ language TEXT,
+ title TEXT,
+ pre_header TEXT,
+ subject TEXT,
+ greeting TEXT,
+ text TEXT,
+ button_text TEXT,
+ footer_text TEXT,
+
+ PRIMARY KEY (aggregate_id, message_text_type, language)
+);
+
+GRANT SELECT ON TABLE adminapi.message_texts TO notification;
+GRANT SELECT ON TABLE management.message_texts TO notification;
+ALTER TABLE management.message_texts OWNER TO admin;
+ALTER TABLE adminapi.message_texts OWNER TO admin;
+
diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto
index 9a82e90db7..a9c277ec0b 100644
--- a/proto/zitadel/admin.proto
+++ b/proto/zitadel/admin.proto
@@ -6,6 +6,7 @@ import "zitadel/object.proto";
import "zitadel/options.proto";
import "zitadel/org.proto";
import "zitadel/policy.proto";
+import "zitadel/text.proto";
import "zitadel/member.proto";
import "zitadel/features.proto";
@@ -1447,28 +1448,137 @@ service AdminService {
option (zitadel.v1.auth_option) = {
permission: "iam.policy.write";
};
+ }
- option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
- tags: "policy";
- tags: "password policy";
- tags: "password lockout policy";
- responses: {
- key: "200";
- value: {
- description: "default password lockout policy updated";
- };
- };
- responses: {
- key: "400";
- value: {
- description: "invalid argument";
- schema: {
- json_schema: {
- ref: "#/definitions/rpcStatus";
- };
- };
- };
- };
+ //Returns the custom text for initial message
+ rpc GetDefaultInitMessageText(GetDefaultInitMessageTextRequest) returns (GetDefaultInitMessageTextResponse) {
+ option (google.api.http) = {
+ get: "/text/message/init/{language}";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.policy.read";
+ };
+ }
+
+ //Sets the default custom text for initial message
+ // it impacts all organisations without customized initial message text
+ // The Following Variables can be used:
+ // {{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
+ rpc SetDefaultInitMessageText(SetDefaultInitMessageTextRequest) returns (SetDefaultInitMessageTextResponse) {
+ option (google.api.http) = {
+ put: "/text/message/init/{language}";
+ body: "*";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.policy.write";
+ };
+ }
+
+ //Returns the custom text for password reset message
+ rpc GetDefaultPasswordResetMessageText(GetDefaultPasswordResetMessageTextRequest) returns (GetDefaultPasswordResetMessageTextResponse) {
+ option (google.api.http) = {
+ get: "/text/message/passwordreset/{language}";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.policy.read";
+ };
+ }
+
+ //Sets the default custom text for password reset message
+ // it impacts all organisations without customized password reset message text
+ // The Following Variables can be used:
+ // {{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
+ rpc SetDefaultPasswordResetMessageText(SetDefaultPasswordResetMessageTextRequest) returns (SetDefaultPasswordResetMessageTextResponse) {
+ option (google.api.http) = {
+ put: "/text/message/passwordreset/{language}";
+ body: "*";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.policy.write";
+ };
+
+ }
+
+ //Returns the custom text for verify email message
+ rpc GetDefaultVerifyEmailMessageText(GetDefaultVerifyEmailMessageTextRequest) returns (GetDefaultVerifyEmailMessageTextResponse) {
+ option (google.api.http) = {
+ get: "/text/message/verifyemail/{language}";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.policy.read";
+ };
+ }
+
+ //Sets the default custom text for verify email message
+ // it impacts all organisations without customized verify email message text
+ // The Following Variables can be used:
+ // {{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
+ rpc SetDefaultVerifyEmailMessageText(SetDefaultVerifyEmailMessageTextRequest) returns (SetDefaultVerifyEmailMessageTextResponse) {
+ option (google.api.http) = {
+ put: "/text/message/verifyemail/{language}";
+ body: "*";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.policy.write";
+ };
+ }
+
+ //Returns the custom text for verify phone message
+ rpc GetDefaultVerifyPhoneMessageText(GetDefaultVerifyPhoneMessageTextRequest) returns (GetDefaultVerifyPhoneMessageTextResponse) {
+ option (google.api.http) = {
+ get: "/text/message/verifyphone/{language}";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.policy.read";
+ };
+
+ }
+
+ //Sets the default custom text for verify phone message
+ // it impacts all organisations without customized verify phone message text
+ // The Following Variables can be used:
+ // {{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
+ rpc SetDefaultVerifyPhoneMessageText(SetDefaultVerifyPhoneMessageTextRequest) returns (SetDefaultVerifyPhoneMessageTextResponse) {
+ option (google.api.http) = {
+ put: "/text/message/verifyphone/{language}";
+ body: "*";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.policy.write";
+ };
+ }
+
+ //Returns the custom text for domain claimed message
+ rpc GetDefaultDomainClaimedMessageText(GetDefaultDomainClaimedMessageTextRequest) returns (GetDefaultDomainClaimedMessageTextResponse) {
+ option (google.api.http) = {
+ get: "/text/message/domainclaimed/{language}";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.policy.read";
+ };
+ }
+
+ //Sets the default custom text for domain claimed phone message
+ // it impacts all organisations without customized verify phone message text
+ // The Following Variables can be used:
+ // {{.Domain}} {{.TempUsername}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
+ rpc SetDefaultDomainClaimedMessageText(SetDefaultDomainClaimedMessageTextRequest) returns (SetDefaultDomainClaimedMessageTextResponse) {
+ option (google.api.http) = {
+ put: "/text/message/verifyphone/{language}";
+ body: "*";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.policy.write";
};
}
@@ -2286,6 +2396,7 @@ message SetDefaultFeaturesRequest {
bool login_policy_password_reset = 14;
bool label_policy_private_label = 15;
bool label_policy_watermark = 16;
+ bool custom_text = 17;
}
message SetDefaultFeaturesResponse {
@@ -2319,6 +2430,7 @@ message SetOrgFeaturesRequest {
bool login_policy_password_reset = 15;
bool label_policy_private_label = 16;
bool label_policy_watermark = 17;
+ bool custom_text = 18;
}
message SetOrgFeaturesResponse {
@@ -2779,6 +2891,151 @@ message UpdatePasswordLockoutPolicyResponse {
zitadel.v1.ObjectDetails details = 1;
}
+//This is an empty request
+message GetDefaultInitMessageTextRequest {
+ string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+}
+
+message GetDefaultInitMessageTextResponse {
+ zitadel.text.v1.MessageCustomText custom_text = 1;
+}
+
+message SetDefaultInitMessageTextRequest {
+ string language = 1 [
+ (validate.rules).string = {min_len: 1, max_len: 200},
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"de\""
+ }
+ ];
+ string title = 2 [(validate.rules).string = {max_len: 200}];
+ string pre_header = 3 [(validate.rules).string = {max_len: 200}];
+ string subject = 4 [(validate.rules).string = {max_len: 200}];
+ string greeting = 5 [(validate.rules).string = {max_len: 200}];
+ string text = 6 [(validate.rules).string = {max_len: 1000}];
+ string button_text = 7 [(validate.rules).string = {max_len: 200}];
+ string footer_text = 8 [(validate.rules).string = {max_len: 200}];
+}
+
+message SetDefaultInitMessageTextResponse {
+ zitadel.v1.ObjectDetails details = 1;
+}
+
+//This is an empty request
+message GetDefaultPasswordResetMessageTextRequest {
+ string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+}
+
+message GetDefaultPasswordResetMessageTextResponse {
+ zitadel.text.v1.MessageCustomText custom_text = 1;
+}
+
+message SetDefaultPasswordResetMessageTextRequest {
+ string language = 1 [
+ (validate.rules).string = {min_len: 1, max_len: 200},
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"de\""
+ }
+ ];
+ string title = 2 [(validate.rules).string = {max_len: 200}];
+ string pre_header = 3 [(validate.rules).string = {max_len: 200}];
+ string subject = 4 [(validate.rules).string = {max_len: 200}];
+ string greeting = 5 [(validate.rules).string = {max_len: 200}];
+ string text = 6 [(validate.rules).string = {max_len: 800}];
+ string button_text = 7 [(validate.rules).string = {max_len: 200}];
+ string footer_text = 8 [(validate.rules).string = {max_len: 200}];
+}
+
+message SetDefaultPasswordResetMessageTextResponse {
+ zitadel.v1.ObjectDetails details = 1;
+}
+
+//This is an empty request
+message GetDefaultVerifyEmailMessageTextRequest {
+ string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+}
+
+message GetDefaultVerifyEmailMessageTextResponse {
+ zitadel.text.v1.MessageCustomText custom_text = 1;
+}
+
+message SetDefaultVerifyEmailMessageTextRequest {
+ string language = 1 [
+ (validate.rules).string = {min_len: 1, max_len: 200},
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"de\""
+ }
+ ];
+ string title = 2 [(validate.rules).string = {max_len: 200}];
+ string pre_header = 3 [(validate.rules).string = {max_len: 200}];
+ string subject = 4 [(validate.rules).string = {max_len: 200}];
+ string greeting = 5 [(validate.rules).string = {max_len: 200}];
+ string text = 6 [(validate.rules).string = {max_len: 800}];
+ string button_text = 7 [(validate.rules).string = {max_len: 200}];
+ string footer_text = 8 [(validate.rules).string = {max_len: 200}];
+}
+
+message SetDefaultVerifyEmailMessageTextResponse {
+ zitadel.v1.ObjectDetails details = 1;
+}
+
+//This is an empty request
+message GetDefaultVerifyPhoneMessageTextRequest {
+ string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+}
+
+message GetDefaultVerifyPhoneMessageTextResponse {
+ zitadel.text.v1.MessageCustomText custom_text = 1;
+}
+
+message SetDefaultVerifyPhoneMessageTextRequest {
+ string language = 1 [
+ (validate.rules).string = {min_len: 1, max_len: 200},
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"de\""
+ }
+ ];
+ string title = 2 [(validate.rules).string = {max_len: 200}];
+ string pre_header = 3 [(validate.rules).string = {max_len: 200}];
+ string subject = 4 [(validate.rules).string = {max_len: 200}];
+ string greeting = 5 [(validate.rules).string = {max_len: 200}];
+ string text = 6 [(validate.rules).string = {max_len: 800}];
+ string button_text = 7 [(validate.rules).string = {max_len: 200}];
+ string footer_text = 8 [(validate.rules).string = {max_len: 200}];
+}
+
+message SetDefaultVerifyPhoneMessageTextResponse {
+ zitadel.v1.ObjectDetails details = 1;
+}
+
+//This is an empty request
+message GetDefaultDomainClaimedMessageTextRequest {
+ string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+}
+
+message GetDefaultDomainClaimedMessageTextResponse {
+ zitadel.text.v1.MessageCustomText custom_text = 1;
+}
+
+message SetDefaultDomainClaimedMessageTextRequest {
+ string language = 1 [
+ (validate.rules).string = {min_len: 1, max_len: 200},
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"de\""
+ }
+ ];
+ string title = 2 [(validate.rules).string = {max_len: 200}];
+ string pre_header = 3 [(validate.rules).string = {max_len: 200}];
+ string subject = 4 [(validate.rules).string = {max_len: 200}];
+ string greeting = 5 [(validate.rules).string = {max_len: 200}];
+ string text = 6 [(validate.rules).string = {max_len: 800}];
+ string button_text = 7 [(validate.rules).string = {max_len: 200}];
+ string footer_text = 8 [(validate.rules).string = {max_len: 200}];
+}
+
+message SetDefaultDomainClaimedMessageTextResponse {
+ zitadel.v1.ObjectDetails details = 1;
+}
+
message AddIAMMemberRequest {
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
json_schema: {
diff --git a/proto/zitadel/features.proto b/proto/zitadel/features.proto
index dd590d08df..346f5dde13 100644
--- a/proto/zitadel/features.proto
+++ b/proto/zitadel/features.proto
@@ -24,6 +24,7 @@ message Features {
bool login_policy_password_reset = 13;
bool label_policy_private_label = 14;
bool label_policy_watermark = 15;
+ bool custom_text = 16;
}
message FeatureTier {
diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto
index d61f7bfb52..0aecd1c925 100644
--- a/proto/zitadel/management.proto
+++ b/proto/zitadel/management.proto
@@ -9,6 +9,7 @@ import "zitadel/org.proto";
import "zitadel/member.proto";
import "zitadel/project.proto";
import "zitadel/policy.proto";
+import "zitadel/text.proto";
import "zitadel/message.proto";
import "zitadel/change.proto";
import "zitadel/auth_n_key.proto";
@@ -2077,6 +2078,200 @@ service ManagementService {
};
}
+ //Returns the custom text for initial message
+ rpc GetCustomInitMessageText(GetCustomInitMessageTextRequest) returns (GetCustomInitMessageTextResponse) {
+ option (google.api.http) = {
+ get: "/text/message/init/{language}";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.policy.read";
+ };
+ }
+
+ //Sets the default custom text for initial message
+ // it impacts all organisations without customized initial message text
+ // The Following Variables can be used:
+ // {{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
+ rpc SetCustomInitMessageText(SetCustomInitMessageTextRequest) returns (SetCustomInitMessageTextResponse) {
+ option (google.api.http) = {
+ put: "/text/message/init/{language}";
+ body: "*";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.policy.write";
+ feature: "custom_text"
+ };
+ }
+
+ // Removes the custom init message text of the organisation
+ // The default text of the IAM will trigger after
+ rpc ResetCustomInitMessageTextToDefault(ResetCustomInitMessageTextToDefaultRequest) returns (ResetCustomInitMessageTextToDefaultResponse) {
+ option (google.api.http) = {
+ delete: "/text/message/init/{language}"
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "policy.delete"
+ };
+ }
+ //Returns the custom text for password reset message
+ rpc GetCustomPasswordResetMessageText(GetCustomPasswordResetMessageTextRequest) returns (GetCustomPasswordResetMessageTextResponse) {
+ option (google.api.http) = {
+ get: "/text/message/passwordreset/{language}";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.policy.read";
+ };
+ }
+
+ //Sets the default custom text for password reset message
+ // it impacts all organisations without customized password reset message text
+ // The Following Variables can be used:
+ // {{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
+ rpc SetCustomPasswordResetMessageText(SetCustomPasswordResetMessageTextRequest) returns (SetCustomPasswordResetMessageTextResponse) {
+ option (google.api.http) = {
+ put: "/text/message/passwordreset/{language}";
+ body: "*";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.policy.write";
+ feature: "custom_text"
+ };
+ }
+
+ // Removes the custom init message text of the organisation
+ // The default text of the IAM will trigger after
+ rpc ResetCustomPasswordResetMessageTextToDefault(ResetCustomPasswordResetMessageTextToDefaultRequest) returns (ResetCustomPasswordResetMessageTextToDefaultResponse) {
+ option (google.api.http) = {
+ delete: "/text/message/verifyemail/{language}"
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "policy.delete"
+ };
+ }
+
+ //Returns the custom text for verify email message
+ rpc GetCustomVerifyEmailMessageText(GetCustomVerifyEmailMessageTextRequest) returns (GetCustomVerifyEmailMessageTextResponse) {
+ option (google.api.http) = {
+ get: "/text/message/verifyemail/{language}";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.policy.read";
+ };
+ }
+
+ //Sets the default custom text for verify email message
+ // it impacts all organisations without customized verify email message text
+ // The Following Variables can be used:
+ // {{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
+ rpc SetCustomVerifyEmailMessageText(SetCustomVerifyEmailMessageTextRequest) returns (SetCustomVerifyEmailMessageTextResponse) {
+ option (google.api.http) = {
+ put: "/text/message/verifyemail/{language}";
+ body: "*";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.policy.write";
+ feature: "custom_text"
+ };
+ }
+
+ // Removes the custom init message text of the organisation
+ // The default text of the IAM will trigger after
+ rpc ResetCustomVerifyEmailMessageTextToDefault(ResetCustomVerifyEmailMessageTextToDefaultRequest) returns (ResetCustomVerifyEmailMessageTextToDefaultResponse) {
+ option (google.api.http) = {
+ delete: "/text/message/verifyemail/{language}"
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "policy.delete"
+ };
+ }
+
+ //Returns the custom text for verify email message
+ rpc GetCustomVerifyPhoneMessageText(GetCustomVerifyPhoneMessageTextRequest) returns (GetCustomVerifyPhoneMessageTextResponse) {
+ option (google.api.http) = {
+ get: "/text/message/verifyphone/{language}";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.policy.read";
+ };
+ }
+
+ //Sets the default custom text for verify email message
+ // it impacts all organisations without customized verify email message text
+ // The Following Variables can be used:
+ // {{.Code}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
+ rpc SetCustomVerifyPhoneMessageText(SetCustomVerifyPhoneMessageTextRequest) returns (SetCustomVerifyPhoneMessageTextResponse) {
+ option (google.api.http) = {
+ put: "/text/message/verifyphone/{language}";
+ body: "*";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.policy.write";
+ feature: "custom_text"
+ };
+ }
+
+ // Removes the custom init message text of the organisation
+ // The default text of the IAM will trigger after
+ rpc ResetCustomVerifyPhoneMessageTextToDefault(ResetCustomVerifyPhoneMessageTextToDefaultRequest) returns (ResetCustomVerifyPhoneMessageTextToDefaultResponse) {
+ option (google.api.http) = {
+ delete: "/text/message/verifyphone/{language}"
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "policy.delete"
+ };
+ }
+
+ //Returns the custom text for domain claimed message
+ rpc GetCustomDomainClaimedMessageText(GetCustomDomainClaimedMessageTextRequest) returns (GetCustomDomainClaimedMessageTextResponse) {
+ option (google.api.http) = {
+ get: "/text/message/domainclaimed/{language}";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.policy.read";
+ };
+ }
+
+ // Sets the default custom text for domain claimed message
+ // it impacts all organisations without customized domain claimed message text
+ // The Following Variables can be used:
+ // {{.Domain}} {{.TempUsername}} {{.UserName}} {{.FirstName}} {{.LastName}} {{.NickName}} {{.DisplayName}} {{.LastEmail}} {{.VerifiedEmail}} {{.LastPhone}} {{.VerifiedPhone}} {{.PreferredLoginName}} {{.LoginNames}} {{.ChangeDate}}
+ rpc SetCustomDomainClaimedMessageCustomText(SetCustomDomainClaimedMessageTextRequest) returns (SetCustomDomainClaimedMessageTextResponse) {
+ option (google.api.http) = {
+ put: "/text/message/domainclaimed/{language}";
+ body: "*";
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.policy.write";
+ feature: "custom_text"
+ };
+ }
+
+ // Removes the custom init message text of the organisation
+ // The default text of the IAM will trigger after
+ rpc ResetCustomDomainClaimedMessageTextToDefault(ResetCustomDomainClaimedMessageTextToDefaultRequest) returns (ResetCustomDomainClaimedMessageTextToDefaultResponse) {
+ option (google.api.http) = {
+ delete: "/text/message/domainclaimed/{language}"
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "policy.delete"
+ };
+ }
+
// Returns a identity provider configuration of the organisation
rpc GetOrgIDPByID(GetOrgIDPByIDRequest) returns (GetOrgIDPByIDResponse) {
option (google.api.http) = {
@@ -3838,6 +4033,196 @@ message ResetLabelPolicyToDefaultResponse {
zitadel.v1.ObjectDetails details = 1;
}
+//This is an empty request
+message GetCustomInitMessageTextRequest {
+ string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+}
+
+message GetCustomInitMessageTextResponse {
+ zitadel.text.v1.MessageCustomText custom_text = 1;
+}
+
+message SetCustomInitMessageTextRequest {
+ string language = 1 [
+ (validate.rules).string = {min_len: 1, max_len: 200},
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"de\""
+ }
+ ];
+ string title = 2 [(validate.rules).string = {max_len: 200}];
+ string pre_header = 3 [(validate.rules).string = {max_len: 200}];
+ string subject = 4 [(validate.rules).string = {max_len: 200}];
+ string greeting = 5 [(validate.rules).string = {max_len: 200}];
+ string text = 6 [(validate.rules).string = {max_len: 800}];
+ string button_text = 7 [(validate.rules).string = {max_len: 200}];
+ string footer_text = 8 [(validate.rules).string = {max_len: 200}];
+}
+
+message SetCustomInitMessageTextResponse {
+ zitadel.v1.ObjectDetails details = 1;
+}
+
+//This is an empty request
+message ResetCustomInitMessageTextToDefaultRequest {
+ string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+}
+
+message ResetCustomInitMessageTextToDefaultResponse {
+ zitadel.v1.ObjectDetails details = 1;
+}
+
+//This is an empty request
+message GetCustomPasswordResetMessageTextRequest {
+ string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+}
+
+message GetCustomPasswordResetMessageTextResponse {
+ zitadel.text.v1.MessageCustomText custom_text = 1;
+}
+
+message SetCustomPasswordResetMessageTextRequest {
+ string language = 1 [
+ (validate.rules).string = {min_len: 1, max_len: 200},
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"de\""
+ }
+ ];
+ string title = 2 [(validate.rules).string = {max_len: 200}];
+ string pre_header = 3 [(validate.rules).string = {max_len: 200}];
+ string subject = 4 [(validate.rules).string = {max_len: 200}];
+ string greeting = 5 [(validate.rules).string = {max_len: 200}];
+ string text = 6 [(validate.rules).string = {max_len: 800}];
+ string button_text = 7 [(validate.rules).string = {max_len: 200}];
+ string footer_text = 8 [(validate.rules).string = {max_len: 200}];
+}
+
+message SetCustomPasswordResetMessageTextResponse {
+ zitadel.v1.ObjectDetails details = 1;
+}
+
+//This is an empty request
+message ResetCustomPasswordResetMessageTextToDefaultRequest {
+ string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+}
+
+message ResetCustomPasswordResetMessageTextToDefaultResponse {
+ zitadel.v1.ObjectDetails details = 1;
+}
+
+//This is an empty request
+message GetCustomVerifyEmailMessageTextRequest {
+ string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+}
+
+message GetCustomVerifyEmailMessageTextResponse {
+ zitadel.text.v1.MessageCustomText custom_text = 1;
+}
+
+message SetCustomVerifyEmailMessageTextRequest {
+ string language = 1 [
+ (validate.rules).string = {min_len: 1, max_len: 200},
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"de\""
+ }
+ ];
+ string title = 2 [(validate.rules).string = {max_len: 200}];
+ string pre_header = 3 [(validate.rules).string = {max_len: 200}];
+ string subject = 4 [(validate.rules).string = {max_len: 200}];
+ string greeting = 5 [(validate.rules).string = {max_len: 200}];
+ string text = 6 [(validate.rules).string = {max_len: 800}];
+ string button_text = 7 [(validate.rules).string = {max_len: 200}];
+ string footer_text = 8 [(validate.rules).string = {max_len: 200}];
+}
+
+message SetCustomVerifyEmailMessageTextResponse {
+ zitadel.v1.ObjectDetails details = 1;
+}
+
+//This is an empty request
+message ResetCustomVerifyEmailMessageTextToDefaultRequest {
+ string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+}
+
+message ResetCustomVerifyEmailMessageTextToDefaultResponse {
+ zitadel.v1.ObjectDetails details = 1;
+}
+
+//This is an empty request
+message GetCustomVerifyPhoneMessageTextRequest {
+ string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+}
+
+message GetCustomVerifyPhoneMessageTextResponse {
+ zitadel.text.v1.MessageCustomText custom_text = 1;
+}
+
+message SetCustomVerifyPhoneMessageTextRequest {
+ string language = 1 [
+ (validate.rules).string = {min_len: 1, max_len: 200},
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"de\""
+ }
+ ];
+ string title = 2 [(validate.rules).string = {max_len: 200}];
+ string pre_header = 3 [(validate.rules).string = {max_len: 200}];
+ string subject = 4 [(validate.rules).string = {max_len: 200}];
+ string greeting = 5 [(validate.rules).string = {max_len: 200}];
+ string text = 6 [(validate.rules).string = {max_len: 800}];
+ string button_text = 7 [(validate.rules).string = {max_len: 200}];
+ string footer_text = 8 [(validate.rules).string = {max_len: 200}];
+}
+
+message SetCustomVerifyPhoneMessageTextResponse {
+ zitadel.v1.ObjectDetails details = 1;
+}
+
+//This is an empty request
+message ResetCustomVerifyPhoneMessageTextToDefaultRequest {
+ string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+}
+
+message ResetCustomVerifyPhoneMessageTextToDefaultResponse {
+ zitadel.v1.ObjectDetails details = 1;
+}
+
+//This is an empty request
+message GetCustomDomainClaimedMessageTextRequest {
+ string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+}
+
+message GetCustomDomainClaimedMessageTextResponse {
+ zitadel.text.v1.MessageCustomText custom_text = 1;
+}
+
+message SetCustomDomainClaimedMessageTextRequest {
+ string language = 1 [
+ (validate.rules).string = {min_len: 1, max_len: 200},
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"de\""
+ }
+ ];
+ string title = 2 [(validate.rules).string = {max_len: 200}];
+ string pre_header = 3 [(validate.rules).string = {max_len: 200}];
+ string subject = 4 [(validate.rules).string = {max_len: 200}];
+ string greeting = 5 [(validate.rules).string = {max_len: 200}];
+ string text = 6 [(validate.rules).string = {max_len: 800}];
+ string button_text = 7 [(validate.rules).string = {max_len: 200}];
+ string footer_text = 8 [(validate.rules).string = {max_len: 200}];
+}
+
+message SetCustomDomainClaimedMessageTextResponse {
+ zitadel.v1.ObjectDetails details = 1;
+}
+
+//This is an empty request
+message ResetCustomDomainClaimedMessageTextToDefaultRequest {
+ string language = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+}
+
+message ResetCustomDomainClaimedMessageTextToDefaultResponse {
+ zitadel.v1.ObjectDetails details = 1;
+}
+
message GetOrgIDPByIDRequest {
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
diff --git a/proto/zitadel/text.proto b/proto/zitadel/text.proto
new file mode 100644
index 0000000000..0be3598dad
--- /dev/null
+++ b/proto/zitadel/text.proto
@@ -0,0 +1,47 @@
+syntax = "proto3";
+
+import "zitadel/object.proto";
+import "protoc-gen-openapiv2/options/annotations.proto";
+
+package zitadel.text.v1;
+
+option go_package ="github.com/caos/zitadel/pkg/grpc/text";
+
+message MessageCustomText {
+ zitadel.v1.ObjectDetails details = 1;
+ string title = 2 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ description: "custom text for email title"
+ }
+ ];
+ string pre_header = 3 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ description: "custom text for email pre header"
+ }
+ ];
+ string subject = 4 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ description: "custom text for email subject"
+ }
+ ];
+ string greeting = 5 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ description: "custom text for email greeting"
+ }
+ ];
+ string text = 6 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ description: "custom text for email text"
+ }
+ ];
+ string button_text = 7 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ description: "custom text for email button_text"
+ }
+ ];
+ string footer_text = 8 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ description: "custom text for email footer_text"
+ }
+ ];
+}