miabhttp

package module
v0.0.0-...-123002b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 23, 2024 License: MIT Imports: 12 Imported by: 1

README

miabhttp

Mail-in-a-box HTTP API client for Go

This library was designed for use with Mail-in-a-box v56 using the API documentation generated from the MIAB repo at this commit.

That documentation may no longer be accurate. Where the documentation is known to be inaccurate against my own testing of the actual API, I have noted that in the comments for the affected function.

This library should not be considered safe, secure, or well-tested, and you must use it at your own risk. I am not the best programmer to begin with, and this is the first Go I have ever written. So, buyer beware...

To use it, you must first create a miabhttp.Context instance, which holds information about how to login to your MIAB server. You can use a username/password combo, or generate an API key from a username and password, and create a Context with a username and API key, omitting the password. If you make an API key, it is recommended to run the Logout() function when you have finished, in order to expire the API key immediately.

Many functions return an interface{}. This is usually because the return value may be a string, or may be a map[string]interface{}, depending on the returned data from the server. Often, when JSON is returned, a map[string]interface{} may be returned upon success, but a string may be returned upon failure. It is up to you the user/client to look at how the function works as well as my comments about the function and determine the best way to handle these unknown return values.

Errors returned by other libraries are returned back through. Errors returned by my library are wrapped in a very simple MiabError struct, whose internals can be accessed like this (where c is a Context):

if result, err := c.GetMailAliases("invalid_format"); err != nil {
    if errors.As(err, &miabhttp.MiabError{}) {
        miaberror := err.(miabhttp.MiabError)
        fmt.Println("The error occured in the miabhttp function " + miaberror.CallingFunction)
    }
}

Additionally worth noting: I have noticed that the server returns HTTP 400 errors when the request is sane and correct, but the action cannot be completed. For example, trying to remove two-factor authentication from a user that does not have 2FA on their account. This is confusing as a client, because some functions are idempotent and return success in these instances, and other functions return HTTP 400. I mention this solely for your benefit, because it frustrated me a lot while testing this library.

Function names are exactly the same as they are in the API URLs, expect for the first letter being capitalized. Parameters are the same, and return values are the same. For the most part, you should be able to intuit everything based on the documentation and my comments. The only thing that may take getting used to is how I return interfaces{} for JSON returns. See the example code below.

Example usage:

myUser := "foo@example.com"
myMiabServer := "box.example.com"

// The MIAB Login API may return successfully but no API key if you enter a bad
// username/password combo. I have chosen to mimic the API as closely as possible,
// so I also return no error in such an event. Therefore, for this function, you
// must also check if the result is an empty string.
apikey, err := miabhttp.LoginAndReturnAPIKey(myMiabServer, "admin", myUser, "my_secret_password", "", true)
if err != nil || apikey == "" {
  os.Exit(1)
}

miabContext, err := miabhttp.CreateMiabContext(myMiabServer, "admin", myUser, "", apikey, "")
if err != nil {
  os.Exit(1)
}
defer miabContext.Logout()


// Now you have a Context to call any function with

// Get a string (on error) or a []map[string]interface{} on success, since
// this is the format returned by the server, and this library seeks to
// be an accurate implementation
result, err := miabContext.GetDnsCustomARecordsForQName("www.example.com")
if err != nil {
  os.Stderr.WriteString("The website returned the text: " + result.(string) +
    "with an error of: " + err.Error())
  os.Exit(1)
}

// Because we map unknown json, we must explicitly cast the result to the
// format we know it's in (based on the API docs and my comments).
// For example, here, we know the result is a slice with only one value, so
// we get index 0 and cast the value's interface{} to a string since we know
// it's a string.
myIPaddr := result.([]map[string]interface{})[0]["value"].(string)
fmt.Println("The IP for www.example.com is: " + myIPaddr)


// Change/update the DNS record
_, err := miabContext.UpdateDnsCustomARecord("www.example.com", "1.2.3.4")
if err != nil {
  os.Exit(1)
}
// Update Success!


// Do whatever else we want to do, then return/exit and let our defered
// Logout() invalidate the API key.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func LoginAndReturnAPIKey

func LoginAndReturnAPIKey(server, apipath, username, password, otpcode string, https bool) (string, error)

This performs a login without using a context, in order to keep the password scope inside this function, so it will be GC'd ASAP. If the return err is nil, the string returned will be the apikey or an empty string if the login credentials were invalid. All other scenarios should return an error. Much of the code is copied from doTheThing() and makeUrl(). This is the only exportable function in this library that does not need the Context struct.

Types

type Context

type Context struct {
	// contains filtered or unexported fields
}

func CreateMiabContext

func CreateMiabContext(server, apipath, username, password, apitoken, otpcode string) (*Context, error)

See Context struct comments for notes on parameter requirements

func (*Context) APIPath

func (c *Context) APIPath() string

func (*Context) APIToken

func (c *Context) APIToken() string

func (*Context) AddDnsCustomRecord

func (c *Context) AddDnsCustomRecord(qname, rtype, value string) (string, error)

https://mailinabox.email/api-docs.html#operation/addDnsCustomRecord

func (*Context) AddMailUser

func (c *Context) AddMailUser(email, password, privileges string) (string, error)

https://mailinabox.email/api-docs.html#operation/addMailUser

func (*Context) AddMailUserPrivilege

func (c *Context) AddMailUserPrivilege(email, privilege string) (string, error)

https://mailinabox.email/api-docs.html#operation/addMailUserPrivilege Note that although the docs say the privilege param can be admin or empty str, it will return a 400 bad request if you try to remove the privilege "", so for now, I ignore the input param and always set admin. Even so, the function signature is left as is since that's what the upstream docs have.

func (*Context) GenerateSSLCSR

func (c *Context) GenerateSSLCSR(domain, countrycode string) (string, error)

https://mailinabox.email/api-docs.html#operation/generateSSLCSR

func (*Context) GetDnsCustomARecordsForQName

func (c *Context) GetDnsCustomARecordsForQName(qname string) (interface{}, error)

https://mailinabox.email/api-docs.html#operation/getDnsCustomARecordsForQName See comments on GetDnsCustomRecords() for explanation of return values

func (*Context) GetDnsCustomRecords

func (c *Context) GetDnsCustomRecords() (interface{}, error)

https://mailinabox.email/api-docs.html#operation/getDnsCustomRecords returns []map[string]interface{} if err == nil, otherwise returns string

Example slice item of map[string]interface{} with all values:

{
	"qname": "box.example.com",
	"rtype": "A",
	"sort-order": {
		"created": 0,
		"qname": 0
	},
	"value": "1.2.3.4",
	"zone": "example.com"
}

The interface{} value of the map is a string for everything but sort-order, which is a map[string]int

func (*Context) GetDnsCustomRecordsForQNameAndType

func (c *Context) GetDnsCustomRecordsForQNameAndType(qname, rtype string) (interface{}, error)

https://mailinabox.email/api-docs.html#operation/getDnsCustomRecordsForQNameAndType See comments on GetDnsCustomRecords() for explanation of return values

func (*Context) GetDnsDump

func (c *Context) GetDnsDump() (interface{}, error)

https://mailinabox.email/api-docs.html#operation/getDnsDump Returns [][]interface{} if err is not nil, where interface{} may be a string hostname or it may be a map[string]string with the keys "explanation", "qname", "rtype", and "value".

Example:

[
	[
		"box.example.com",
		{
			"qname": "asdf.box.example.com",
			"rtype": "A",
			"value": "1.2.3.4",
			"explanation": "(Set by user.)"
		}
	],
	[
		"box2.example.com",
		{
			"qname": "asdf.box2.example.com",
			"rtype": "A",
			"value": "1.2.3.5",
			"explanation": "(Set by user.)"
		}
	]
]

func (*Context) GetDnsSecondaryNameserver

func (c *Context) GetDnsSecondaryNameserver() (interface{}, error)

https://mailinabox.email/api-docs.html#operation/getDnsSecondaryNameserver Will return a string when there is an error, but if err == nil, then the return will be a map[string][]string

func (*Context) GetDnsZones

func (c *Context) GetDnsZones() (interface{}, error)

https://mailinabox.email/api-docs.html#operation/getDnsZones returns a slice of strings if err != nil, otherwise return value is a string

func (*Context) GetMailAliases

func (c *Context) GetMailAliases(format string) (interface{}, error)

https://mailinabox.email/api-docs.html#operation/getMailAliases See comments for GetMailUsers() function for information about return types. It works the same here.

func (*Context) GetMailUserPrivileges

func (c *Context) GetMailUserPrivileges(email string) (string, error)

https://mailinabox.email/api-docs.html#operation/getMailUserPrivileges Returns empty str with no err if user is not admin; returns 400 bad request err if (among other possibilities) the user doesn't exist

func (*Context) GetMailUsers

func (c *Context) GetMailUsers(format string) (interface{}, error)

https://mailinabox.email/api-docs.html#operation/getMailUsers The returned interface{} will be type string or []map[string]interface{} depending on which format you choose. However, if you choose json and and a non-200 status is returned, the return will still be string, so you must check if error is nil. The return value will only be a []map[string]interface{} if format is json and response status code is 200. This is done to keep in line with how the MIAB HTTP API actually works.

func (*Context) GetSSLStatus

func (c *Context) GetSSLStatus() (interface{}, error)

https://mailinabox.email/api-docs.html#operation/getSSLStatus Returns string if err != nil and map[string][]interface{}{} if no error.

func (*Context) GetSystemBackupConfig

func (c *Context) GetSystemBackupConfig() (interface{}, error)

https://mailinabox.email/api-docs.html#operation/getSystemBackupConfig

func (*Context) GetSystemBackupStatus

func (c *Context) GetSystemBackupStatus() (interface{}, error)

https://mailinabox.email/api-docs.html#operation/getSystemBackupStatus

func (*Context) GetSystemPrivacyStatus

func (c *Context) GetSystemPrivacyStatus() (interface{}, error)

https://mailinabox.email/api-docs.html#operation/getSystemPrivacyStatus return value will be string if there's an error, but will be a bool if err == nil

func (*Context) GetSystemRebootStatus

func (c *Context) GetSystemRebootStatus() (interface{}, error)

https://mailinabox.email/api-docs.html#operation/getSystemRebootStatus

func (*Context) GetSystemStatus

func (c *Context) GetSystemStatus() (interface{}, error)

https://mailinabox.email/api-docs.html#operation/getSystemStatus

func (*Context) GetWebDomains

func (c *Context) GetWebDomains() (interface{}, error)

https://mailinabox.email/api-docs.html#operation/getWebDomains

func (*Context) InsertAPITokenAndDeletePassword

func (c *Context) InsertAPITokenAndDeletePassword(apitoken string) error

func (*Context) InstallSSLCertificate

func (c *Context) InstallSSLCertificate(domain, cert string, chain interface{}) (string, error)

https://mailinabox.email/api-docs.html#operation/installSSLCertificate

func (*Context) Login

func (c *Context) Login() (interface{}, error)

https://mailinabox.email/api-docs.html#operation/login Return value is usually a map[string]interface{}, but may be string on non-200 status codes that return strings.

func (*Context) Logout

func (c *Context) Logout() (interface{}, error)

https://mailinabox.email/api-docs.html#operation/logout Return value is usually a map[string]interface{}, but may be string on non-200 status codes that return strings.

func (*Context) MfaStatus

func (c *Context) MfaStatus() (interface{}, error)

https://mailinabox.email/api-docs.html#operation/mfaStatus The docs say that enabled_mfa returns a single object, but in fact it returns an array of objects. Similarly, new_mfa does not return a map[string]string as the docs say, but instead returns a map[string]map[string]string, with the outer map having only the key "totp". This is likely so that in the future, multiple kinds of 2FA can be added, but the docs make no mention of this.

func (*Context) MfaTotpDisable

func (c *Context) MfaTotpDisable(mfa_id, user interface{}) (string, error)

https://mailinabox.email/api-docs.html#operation/mfaTotpDisable mfa_id can be the id or the label, or nil to disable 2FA for all users; user can be nil or string user

func (*Context) MfaTotpEnable

func (c *Context) MfaTotpEnable(secret, code string, label interface{}) (string, error)

https://mailinabox.email/api-docs.html#operation/mfaTotpEnable The docs use the word "code" but the actual parameter is "token". (You can discover the real API by reading the javascript used in the admin panel page of your MIAB instance.) Also, the server will return 400 status if the secret is not a base32 string of length 32.

func (*Context) OTPcode

func (c *Context) OTPcode() string

func (*Context) Password

func (c *Context) Password() string

func (*Context) ProvisionSSLCertificates

func (c *Context) ProvisionSSLCertificates() (interface{}, error)

https://mailinabox.email/api-docs.html#operation/provisionSSLCertificates

func (*Context) RemoveDnsCustomRecord

func (c *Context) RemoveDnsCustomRecord(qname, rtype, value string) (string, error)

https://mailinabox.email/api-docs.html#operation/removeDnsCustomRecord

func (*Context) RemoveMailUserPrivilege

func (c *Context) RemoveMailUserPrivilege(email, privilege string) (string, error)

https://mailinabox.email/api-docs.html#operation/removeMailUserPrivilege Note that although the docs say the privilege param can be admin or empty str, it will return a 400 bad request if you try to remove the privilege "", so for now, I ignore the input param and always set admin. Even so, the function signature is left as is since that's what the upstream docs have.

func (*Context) Server

func (c *Context) Server() string

func (*Context) SetMailUserPassword

func (c *Context) SetMailUserPassword(email, password string) (string, error)

https://mailinabox.email/api-docs.html#operation/setMailUserPassword Returns 400 bad request err if (among other possibilities) the user doesn't exist

func (*Context) UpdateDnsCustomRecord

func (c *Context) UpdateDnsCustomRecord(qname, rtype, value string) (string, error)

https://mailinabox.email/api-docs.html#operation/updateDnsCustomRecord

func (*Context) UpdateSystemBackupConfig

func (c *Context) UpdateSystemBackupConfig(target, target_user, target_pass string, min_age int) (string, error)

https://mailinabox.email/api-docs.html#operation/updateSystemBackupConfig This has never actually been tested, because I didn't want to mess with my backups. (Everything else was tested on my personal MIAB setup that I actually use.) So BE CAREFUL when using this function because it might not work...

func (*Context) UpsertMailAlias

func (c *Context) UpsertMailAlias(update_if_exists int, address, forwards_to string, permitted_senders interface{}) (string, error)

https://mailinabox.email/api-docs.html#operation/upsertMailAlias

func (*Context) Username

func (c *Context) Username() string

type MiabError

type MiabError struct {
	IsHTTPStatusError bool
	HTTPStatusCode    int
	CallingFunction   string
	ErrorMsg          string
}

func (MiabError) Error

func (m MiabError) Error() string

func (MiabError) String

func (m MiabError) String() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL