Documentation ¶
Overview ¶
Req: go 1.16 or later (embed.FS is N/A on Go 1.15 or lower)
Req: go 1.16 or later (embed.FS is N/A on Go 1.15 or lower)
Req: go 1.16 or later (embed.FS is N/A on Go 1.15 or lower)
Req: go 1.16 or later (embed.FS is N/A on Go 1.15 or lower)
Req: go 1.16 or later (embed.FS is N/A on Go 1.15 or lower)
Req: go 1.16 or later (embed.FS is N/A on Go 1.15 or lower)
Req: go 1.16 or later (embed.FS is N/A on Go 1.15 or lower)
Req: go 1.16 or later (embed.FS is N/A on Go 1.15 or lower)
Req: go 1.16 or later (embed.FS is N/A on Go 1.15 or lower)
Req: go 1.16 or later (embed.FS is N/A on Go 1.15 or lower)
Req: go 1.16 or later (embed.FS is N/A on Go 1.15 or lower)
Req: go 1.16 or later (embed.FS is N/A on Go 1.15 or lower)
Req: go 1.16 or later (embed.FS is N/A on Go 1.15 or lower)
Req: go 1.16 or later (embed.FS is N/A on Go 1.15 or lower)
Req: go 1.16 or later (embed.FS is N/A on Go 1.15 or lower)
Index ¶
- Constants
- Variables
- func ApiFloodControlClearVisitFactorsPerClientIP(r *http.Request, area string) int64
- func ApiFloodControlCountVisitFactorsPerClientIP(r *http.Request, area string) int64
- func ApiFloodControlRegisterClientIPVisit(r *http.Request, area string, factor uint8) int64
- func ApiResponseJsonERR(errCode uint16, errMsg string, data any) string
- func ApiResponseJsonOK(data any) string
- func AuthTokenJwtAlgoGet() string
- func AuthTokenJwtAlgoSet(jwtAlgo string) bool
- func AuthTokenJwtAlgoValidGet(algo string) string
- func AuthTokenJwtIsEnabled() bool
- func DecryptUrlValue(val string) string
- func EncryptUrlValue(val string) string
- func Get2FATotp(secret string) (string, *otp.TOTP, error)
- func GetAllPostVars(r *http.Request) map[string][]string
- func GetAllRequestVars(r *http.Request) map[string][]string
- func GetAllUrlQueryVars(r *http.Request) map[string][]string
- func GetAuthRealm() string
- func GetBaseBrowserPath() string
- func GetBaseDomainDomainPort(r *http.Request) (string, string, string, error)
- func GetBasePath() string
- func GetClientIdentAppSafeSignature(r *http.Request) string
- func GetClientIdentUidHash(r *http.Request) string
- func GetClientMimeAcceptHeaders(r *http.Request) []string
- func GetCookie(r *http.Request, name string) string
- func GetCurrentBaseUrl(r *http.Request) string
- func GetCurrentBrowserBaseUrl(r *http.Request) string
- func GetCurrentBrowserPath(r *http.Request) string
- func GetCurrentBrowserProtocol(r *http.Request) string
- func GetCurrentBrowserUrl(r *http.Request, withUrlQuery bool) string
- func GetCurrentPath(r *http.Request) string
- func GetCurrentProtocol(r *http.Request) string
- func GetCurrentUrl(r *http.Request, withUrlQuery bool) string
- func GetCurrentYear() string
- func GetPostVar(r *http.Request, key string) string
- func GetPostVars(r *http.Request, key string) []string
- func GetRequestVar(r *http.Request, key string) string
- func GetRequestVars(r *http.Request, key string) []string
- func GetUrlQueryVar(r *http.Request, key string) string
- func GetUrlQueryVars(r *http.Request, key string) []string
- func GetUrlRawQuery(r *http.Request) string
- func GetUuidCookieName() string
- func GetUuidCookieValue(r *http.Request) string
- func GetVisitorRealIpAddr(r *http.Request) (bool, string)
- func GetVisitorRemoteIpAddrAndPort(r *http.Request) (string, string)
- func HttpRequestGetHeaderStr(r *http.Request, hdrKey string) string
- func Is2FATotpSecretValid(secret string) bool
- func IsAjaxRequest(r *http.Request) bool
- func IsSessUUIDCookieValid(crrUUIDCookieVal string) bool
- func JsonValidateWithSchema(draft uint16, schema string, json string) error
- func JwtAudienceIsDefaultArea(jwtAudience JwtAudience) bool
- func JwtAudienceIsDefaultPrivs(jwtAudience JwtAudience) bool
- func JwtAudienceIsDefaultRestr(jwtAudience JwtAudience) bool
- func JwtGetFullNameSigningAlgo(jwtSignMethod string) string
- func JwtNewAudience(ipList string, area string, privs string, restr string, cliSign string) []string
- func JwtVerifyWithPublicKey(tokenString string, jwtSignMethod string, clientIP string, dom string, ...) error
- func JwtVerifyWithUserPrivKey(tokenString string, jwtSignMethod string, clientIP string, dom string, ...) error
- func ParseForm(r *http.Request) error
- func ParseMultipartForm(r *http.Request) error
- func ParseUrlRawQuery(urlQuery string) map[string][]string
- func RequestHaveQueryString(r *http.Request) bool
- func SetWebAuthAccountsMethods(methods WebAuthAccountsMethods) error
- func UrlHandlerRegisterRoute(route string, skipAuth bool, methods []string, maxTailSegments int, ...) bool
- func UrlHandlerUnRegisterRoute(route string) bool
- func WebDirExists(path string) bool
- func WebDirIsValid(path string) bool
- func WebFileExists(path string) bool
- func WebPathExists(path string) bool
- func WebPathIsValid(path string) bool
- func WebServerRun(servePublicPath bool, webdavOptions *WebdavRunOptions, serveSecure bool, ...) int16
- func WebServerSetMaxPostSize(size uint64) bool
- func WebUrlPathIsValid(urlPath string) bool
- func WebUrlRouteIsValid(route string) bool
- type HttpHandlerFunc
- type HttpResponse
- type JwtAudience
- type JwtClaims
- type JwtData
- type JwtTokenData
- type MultiPartPostFile
- type WebAuthAccountsMethods
- type WebAuthGetAuthUserRecordByUserName
- type WebAuthValidateAuthUserByTokenRecord
- type WebdavRunOptions
Constants ¶
const ( API_FLOOD_CACHE_CLEANUP_INTERVAL uint32 = 5 // 5 seconds REGEX_SAFE_API_FLOOD_AREA string = `^[a-z0-9\.\:]+$` )
const ( JwtDefaultExpirationMinutes int64 = 60 * 24 // 1440 m = 1 d JwtMaxExpirationMinutes int64 = 60 * 24 * 360 // 518400 m = 360 d JwtMinExpirationMinutes int64 = 1 // 1 m JwtMinLength uint16 = 128 // {{{SYNC-AUTH-JWT-MIN-ALLOWED-LEN}}} JwtMaxLength uint16 = 1280 // {{{SYNC-AUTH-JWT-MAX-ALLOWED-LEN}}} JwtRegexSerial string = `^[A-Z0-9]{10}\-[A-Z0-9]{10}$` )
const ( VERSION string = "r.20250118.2358" SIGNATURE string = smart.COPYRIGHT SERVE_HTTP2 bool = false // HTTP2 still have many bugs and many security flaws, disable SERVER_ADDR string = "127.0.0.1" // default SERVER_PORT uint16 = 17788 // default WEB_PUBLIC_RELATIVE_ROOT_PATH string = "./web-public/" DEFAULT_DIRECTORY_INDEX_HTML string = "index.html" DAV_PUBLIC_SAFETY_FILE string = "./webdav-allow-public-no-auth" DAV_STORAGE_RELATIVE_ROOT_PATH string = "./webdav" // do not add trailing slash DAV_URL_PATH string = "webdav" CACHED_EXP_TIME_SECONDS uint32 = assets.CACHED_EXP_TIME_SECONDS * 3 // 24h CERTIFICATES_DEFAULT_PATH string = "./ssl/" CERTIFICATE_PEM_CRT string = "cert.crt" CERTIFICATE_PEM_KEY string = "cert.key" )
const (
DEBUG_AUTH bool = false // DO NOT SET this to TRUE in production environments ! it is meant just for development purposes
)
const (
REGEX_SAFE_WEB_ROUTE string = `^[_a-zA-Z0-9\-\.@,;\!\/]+$` // SAFETY: SUPPORT ONLY THESE CHARACTERS IN WEB ROUTES ... ; w3s alike ; must support everything in REGEX_SMART_SAFE_FILE_NAME except: #, includding A-Z which on register route may be disallowed
)
const (
REGEX_SESS_UUID_COOKIE_VALID_VALUE string = `^[A-Za-z0-9]+` // B62
)
const TheStrName string = "SmartGO Web Server"
const TheStrSignature string = TheStrName + " " + VERSION
Variables ¶
var (
DEBUG bool = smart.DEBUG
)
var (
DEBUG_API_CACHE bool = smart.DEBUG
)
Functions ¶
func ApiResponseJsonOK ¶
func AuthTokenJwtAlgoGet ¶
func AuthTokenJwtAlgoGet() string
func AuthTokenJwtAlgoSet ¶
func AuthTokenJwtIsEnabled ¶
func AuthTokenJwtIsEnabled() bool
func DecryptUrlValue ¶
func EncryptUrlValue ¶
func GetAuthRealm ¶
func GetAuthRealm() string
func GetBaseBrowserPath ¶
func GetBaseBrowserPath() string
func GetBaseDomainDomainPort ¶
func GetBasePath ¶
func GetBasePath() string
func GetClientIdentUidHash ¶
func GetCurrentBaseUrl ¶
func GetCurrentBrowserPath ¶
func GetCurrentPath ¶
func GetCurrentProtocol ¶
func GetCurrentYear ¶
func GetCurrentYear() string
func GetUrlRawQuery ¶
func GetUuidCookieName ¶
func GetUuidCookieName() string
func GetUuidCookieValue ¶
func Is2FATotpSecretValid ¶
func IsAjaxRequest ¶
func IsSessUUIDCookieValid ¶
func JsonValidateWithSchema ¶
func JwtAudienceIsDefaultArea ¶
func JwtAudienceIsDefaultArea(jwtAudience JwtAudience) bool
func JwtAudienceIsDefaultPrivs ¶
func JwtAudienceIsDefaultPrivs(jwtAudience JwtAudience) bool
func JwtAudienceIsDefaultRestr ¶
func JwtAudienceIsDefaultRestr(jwtAudience JwtAudience) bool
func JwtNewAudience ¶
func JwtVerifyWithPublicKey ¶
func ParseMultipartForm ¶
func ParseUrlRawQuery ¶
func RequestHaveQueryString ¶
func SetWebAuthAccountsMethods ¶
func SetWebAuthAccountsMethods(methods WebAuthAccountsMethods) error
func UrlHandlerRegisterRoute ¶
func WebDirExists ¶
func WebDirIsValid ¶
func WebFileExists ¶
func WebPathExists ¶
func WebPathIsValid ¶
func WebServerRun ¶
func WebServerRun(servePublicPath bool, webdavOptions *WebdavRunOptions, serveSecure bool, certifPath string, httpAddr string, httpPort uint16, timeoutSeconds uint32, allowedIPs string, authRealm string, authUser string, authPass string, authToken string, customAuthCheck smarthttputils.HttpAuthCheckFunc, rateLimit int, rateBurst int) int16
IMPORTANT: If using Proxy with different PROXY_HTTP_BASE_PATH than "/" (ex: "/api/") the Proxy MUST strip back PROXY_HTTP_BASE_PATH to "/" for this backend
func WebServerSetMaxPostSize ¶
func WebUrlPathIsValid ¶
func WebUrlRouteIsValid ¶
Types ¶
type HttpHandlerFunc ¶
type HttpHandlerFunc func(r *http.Request, headPath string, tailPaths []string, authData smart.AuthDataStruct) (response HttpResponse)
var RouteHandlerAuthApi HttpHandlerFunc = func(r *http.Request, headPath string, tailPaths []string, authData smart.AuthDataStruct) (response HttpResponse) { defer smart.PanicHandler() if len(tailPaths) > 1 { response.StatusCode = 400 response.ContentBody = "Auth Api: Requested Route is Too Long" response.ContentFileName = "400.html" return } //-- var subRoute string = "" if len(tailPaths) > 0 { subRoute = tailPaths[0] } response.StatusCode = 208 response.ContentFileName = "error.json" //-- var jwtSignMethod string = AuthTokenJwtAlgoGet() switch subRoute { case "": if (!IsAjaxRequest(r)) && (RequestHaveQueryString(r)) { response.StatusCode = 302 response.ContentBody = GetCurrentBrowserPath(r) response.ContentFileName = "" return } if r.Method != "GET" { response.ContentBody = ApiResponseJsonERR(405, "Unsupported Request Method: `"+r.Method+"`", nil) return } if authData.OK != true { response.ContentBody = ApiResponseJsonERR(403, "Authentication is Required for this URL", nil) return } if authData.ErrMsg != "" { response.LogMessage = "Authentication Error: `" + authData.ErrMsg + "`" response.ContentBody = ApiResponseJsonERR(500, "Authentication Error", nil) return } if (smart.StrTrimWhitespaces(authData.UserName) == "") || (smart.AuthIsValidExtUserName(authData.UserName) != true) { response.LogMessage = "Authentication Ext. UserName is Empty or Invalid: `" + authData.UserName + "`" response.ContentBody = ApiResponseJsonERR(422, "Authentication Ext. UserName is Empty or Invalid", nil) return } userMetaInfoDateTime, errUserMetaInfoDateTime := smart.AuthGetMetaData(authData, "DateTime") if errUserMetaInfoDateTime != nil { if len(authData.MetaData) > 0 { response.ContentBody = ApiResponseJsonERR(500, "Failed to Get User`s MetaInfo DateTime", errUserMetaInfoDateTime) return } } userMetaInfoDateTime = smart.StrTrimWhitespaces(userMetaInfoDateTime) if userMetaInfoDateTime == "" { if len(authData.MetaData) > 0 { response.ContentBody = ApiResponseJsonERR(500, "Failed to Get User`s MetaInfo DateTime: EMPTY", nil) return } } var tkTyp string = "" if AuthTokenJwtIsEnabled() == true { tkTyp = jwtSignMethod if tkTyp != "" { tkTyp = "JWT:" + tkTyp } } var tkOpqTyp string = "" if smart.AuthTokenIsEnabled() == true { tkOpqTyp = smart.OPAQUE_TOKEN_FULL_NAME } var thePassHash string = authData.PassHash if authData.PassAlgo == smart.ALGO_PASS_PLAIN { thePassHash = "[Encrypted]:" + thePassHash } else if authData.PassAlgo == smart.ALGO_PASS_NONE { if thePassHash != "" { thePassHash = "ERROR:(Not Empty)" } } var safeMetaData map[string]string = nil // safe for display, will hide (mask) sensitive data if authData.MetaData != nil { safeMetaData = map[string]string{} if len(authData.MetaData) > 0 { for kk, vv := range authData.MetaData { var canDisplayData bool = true if smart.StrIContains(kk, "key") { canDisplayData = false } else if smart.StrIContains(kk, "security") { canDisplayData = false } else if smart.StrIContains(kk, "private") { canDisplayData = false } else if smart.StrIContains(kk, "pass") { canDisplayData = false } if !canDisplayData { safeMetaData[kk+":length"] = smart.ConvertIntToStr(len(vv)) } else { safeMetaData[kk] = vv } } } } var jwtInfo *JwtTokenData = nil if authData.PassAlgo == smart.ALGO_PASS_SMART_SAFE_WEB_TOKEN { jwtExInfo := JwtExtractData(authData.TokenData) jwtInfo = &jwtExInfo } metaInfo := authMetaNfo{ Auth2FAEnabled: smart.Auth2FACookieIsEnabled(), AuthBasicEnabled: smart.AuthBasicIsEnabled(), AuthTokenEnabled: smart.AuthTokenIsEnabled(), AuthCookieEnabled: smart.AuthCookieIsEnabled(), AuthBearerEnabled: smart.AuthBearerIsEnabled(), AuthApiKeyEnabled: smart.AuthApikeyIsEnabled(), AuthSignedTokensDefAlgo: tkTyp, AuthOpaqueTokensDefAlgo: tkOpqTyp, } status := authStatusNfo{ Authenticated: authData.OK, AuthErrors: authData.ErrMsg, AuthMethodID: authData.Method, AuthMethodName: "Auth:" + smart.AuthMethodGetNameById(authData.Method), AuthRealm: authData.Realm, AuthArea: authData.Area, AuthUserName: authData.UserName, AuthUserID: authData.UserID, AuthPassHash: thePassHash, AuthPassAlgoID: authData.PassAlgo, AuthPassAlgoName: smart.AuthPassHashAlgoGetNameById(authData.PassAlgo), AuthRawDataSize: uint64(len(authData.RawAuthData)), AuthTokenSize: uint64(len(authData.TokenData)), AuthTokenType: authData.TokenAlgo, AuthEmailAddr: authData.EmailAddr, AuthFullName: authData.FullName, AuthPrivileges: authData.Privileges, AuthRestrictions: authData.Restrictions, AuthSecurityKeySize: uint64(len(authData.SecurityKey)), AuthPrivateKeySize: uint64(len(authData.PrivKey)), AuthPublicKeySize: uint64(len(authData.PubKey)), AuthPublicKey: authData.PubKey, AuthQuota: authData.Quota, AuthMetaData: safeMetaData, AuthJwtToken: jwtInfo, } nfo := authNfo{ Status: &status, MetaInfo: &metaInfo, } if DEBUG_AUTH { nfo.DebugData = &authData } arrAccepts := GetClientMimeAcceptHeaders(r) acceptJson := smart.InListArr(smarthttputils.MIME_TYPE_JSON, arrAccepts) if acceptJson { response.ContentFileName = "auth.json" response.ContentBody = ApiResponseJsonOK(nfo) } else { var bwPath string = GetCurrentBrowserPath(r) var title = "Auth Info" var headHtml string = assets.HTML_CSS_STYLE_PREFER_COLOR_DARK + "\n" var bodyHtml string = "<h1>" + smart.EscapeHtml(title) + "</h1>" + "\n" bodyHtml += `<div class="operation_hint">API access point <i class="sfi sfi-lock sfi-xl" title="Requires Authentication" style="cursor:help;"></i> [Accept: ` + smart.EscapeHtml(smarthttputils.MIME_TYPE_JSON) + `]: <i>` + smart.EscapeHtml("`"+bwPath+"`") + `</i></div>` + "\n" bodyHtml += smart.RenderMarkersTpl(assets.ReadWebAsset("lib/tpl/syntax-highlight-init.inc.mtpl.htm"), map[string]string{ "THEME": "", "AREAS": "body", }) bodyHtml += smart.RenderMarkersTpl(assets.ReadWebAsset("lib/tpl/syntax-highlight-area.inc.mtpl.htm"), map[string]string{ "SYNTAX": "json", "CODE": smart.JsonNoErrChkEncode(nfo, true, false), }) response.ContentBody = srvassets.HtmlServerFaviconTemplate(title, headHtml, bodyHtml, true, assets.GetAuthLogo(false)) response.ContentFileName = "auth.html" } response.StatusCode = 200 return break case "2fatotp": if RequestHaveQueryString(r) { response.StatusCode = 302 response.ContentBody = GetCurrentBrowserPath(r) response.ContentFileName = "" return } if r.Method != "GET" { response.StatusCode = 405 response.LogMessage = "Unsupported Request Method: `" + r.Method + "`" response.ContentBody = response.LogMessage response.ContentFileName = "405.html" return } if authData.OK != true { response.StatusCode = 403 response.LogMessage = "Authentication is Required for this URL" response.ContentBody = response.LogMessage response.ContentFileName = "403.html" return } if authData.ErrMsg != "" { response.StatusCode = 500 response.LogMessage = "Authentication Error: `" + authData.ErrMsg + "`" response.ContentBody = "Authentication Error" response.ContentFileName = "500.html" return } if (authData.Method != smart.HTTP_AUTH_MODE_BASIC) && (authData.Method != smart.HTTP_AUTH_MODE_COOKIE) { response.StatusCode = 406 response.ContentBody = "Authentication Method is Not Accepted for this URL: [" + smart.ConvertUInt8ToStr(authData.Method) + "] / [Auth:" + smart.AuthMethodGetNameById(authData.Method) + "]" response.ContentFileName = "406.html" return } if authData.Area != smart.HTTP_AUTH_DEFAULT_AREA { response.StatusCode = 424 response.ContentBody = "Authentication Area is Not Accepted for this URL" response.ContentFileName = "424.html" return } if smart.AuthSafeTestPrivsRestr(authData.Privileges, smart.HTTP_AUTH_ADMIN_PRIV) != true { response.StatusCode = 403 response.ContentBody = "Authentication Privileges are Not Accepted for this URL" response.ContentFileName = "403.html" return } if (smart.StrTrimWhitespaces(authData.UserName) == "") || (smart.AuthIsValidUserName(authData.UserName) != true) { response.StatusCode = 422 response.ContentBody = "Authentication UserName is Empty or Invalid: `" + authData.UserName + "`" response.ContentFileName = "422.html" return } basedom, dom, port, errDomPort := GetBaseDomainDomainPort(r) if errDomPort != nil { response.StatusCode = 502 response.ContentBody = "Authentication Domain:Port Failed: `" + errDomPort.Error() + "`" response.ContentFileName = "502.html" return } if (basedom == "") || (dom == "") || (port == "") { response.StatusCode = 502 response.ContentBody = "Authentication Domain:Port is Invalid: `" + dom + ":" + port + "` ; base domain is: `" + basedom + "`" response.ContentFileName = "502.html" return } rndSecret, totp, errTotp := Get2FATotp("") if (totp == nil) || (errTotp != nil) { response.StatusCode = 500 response.ContentBody = "TOTP Error" if errTotp != nil { response.ContentBody += ": `" + errTotp.Error() + "`" } response.ContentFileName = "500.html" return } var qrUrl string = totp.GenerateBarcodeUrl(authData.UserName, basedom) svgQR, errSvgQR := qrsvg.New(qrUrl, "M", "#685A8B", "none", true, 4, 2) if errSvgQR != nil { response.StatusCode = 500 response.ContentBody = "QR Code Error: `" + errSvgQR.Error() + "`" response.ContentFileName = "500.html" return } var title = "Auth 2FA TOTP Generator" // var headHtml string = "<style>img.svg { margin:10px; border:1px #EFEFEF solid; }</style>" + "\n" var headHtml string = assets.HTML_CSS_STYLE_PREFER_COLOR_DARK + "\n" + "<style>img.svg { margin:10px; }</style>" + "\n" var bodyHtml string = "<h1>" + smart.EscapeHtml(title) + "</h1>" + "\n" bodyHtml += `<hr>` + "\n" bodyHtml += `<h5>2FA Setup QRCode to use with <i style="color:#ED2839;">FreeOTP App</i> or similar 2FA authenticator apps:</h5><img class="svg" src="` + smart.EscapeHtml(smart.DATA_URL_SVG_IMAGE_PREFIX+smart.EscapeUrl(svgQR.Svg)) + `" title="` + smart.EscapeHtml(qrUrl) + `">` + "\n" bodyHtml += `<br>` + "\n" bodyHtml += `<button class="ux-button ux-button-primary" onclick="self.location = self.location; return false;"><i class="sfi sfi-lg sfi-spinner9"></i> Generate New 2FA TOTP Secret</button>` bodyHtml += `<hr>` + "\n" bodyHtml += `<textarea id="area-secret" class="ux-field" style="width:320px; height:25px; font-size:0.625rem !important; color:#CDCDCD !important;" readonly>` + smart.EscapeHtml(rndSecret) + `</textarea>` + "\n" bodyHtml += `<br>` + "\n" bodyHtml += `<script>const copyElemToClipboard = () => { const err = smartJ$Browser.copyToClipboard('area-secret'); const txt = 'Copy to Clipboard'; const img = '<br><i class="sfi sfi-clipboard"></i>'; if(!!err) { console.error('ERR: copyElemToClipboard:', err); smartJ$Browser.GrowlNotificationAdd(txt, 'FAILED to Copy the Secret to Clipboard' + img, null, 3500, false, 'pink'); } else { smartJ$Browser.GrowlNotificationAdd(txt, 'Secret has been Copied to Clipboard' + img, null, 1500, false, 'blue'); } };</script>` + "\n" bodyHtml += `<button class="ux-button ux-button-small ux-button-details" onclick="copyElemToClipboard(); return false;"><i class="sfi sfi-stack"></i> Copy Secret to Clipboard</button>` + ` <span style="color:#685A8B;">[ username: ` + "`<b>" + smart.EscapeHtml(authData.UserName) + "</b>`" + ` ]</span>` + "\n" bodyHtml += `<br>` + "\n" response.ContentBody = srvassets.HtmlServerFaviconTemplate(title, headHtml, bodyHtml, true, assets.GetAuthLogo(false)) response.StatusCode = 200 response.ContentFileName = "auth-2fatotp.html" return break case "jwt": if r.Method != "GET" { response.LogMessage = "Unsupported Request Method: `" + r.Method + "`" response.ContentBody = ApiResponseJsonERR(405, response.LogMessage, nil) return } if authData.OK != true { response.LogMessage = "Authentication is Required for this URL" response.ContentBody = ApiResponseJsonERR(403, response.LogMessage, nil) return } if authData.ErrMsg != "" { response.LogMessage = "Authentication Error: `" + authData.ErrMsg + "`" response.ContentBody = ApiResponseJsonERR(500, "Authentication Error", nil) return } if (authData.Method != smart.HTTP_AUTH_MODE_BASIC) && (authData.Method != smart.HTTP_AUTH_MODE_COOKIE) && (authData.Method != smart.HTTP_AUTH_MODE_BEARER) { response.ContentBody = ApiResponseJsonERR(406, "Authentication Method is Not Accepted for this URL: ["+smart.ConvertUInt8ToStr(authData.Method)+"] / [Auth:"+smart.AuthMethodGetNameById(authData.Method)+"]", nil) return } if authData.Area != smart.HTTP_AUTH_DEFAULT_AREA { response.ContentBody = ApiResponseJsonERR(424, "Authentication Area is Not Accepted for this URL", nil) return } if smart.AuthSafeTestPrivsRestr(authData.Privileges, smart.HTTP_AUTH_ADMIN_PRIV) != true { response.ContentBody = ApiResponseJsonERR(403, "Authentication Privileges are Not Accepted for this URL", nil) return } if AuthTokenJwtIsEnabled() != true { response.ContentBody = ApiResponseJsonERR(501, "Authentication JWT is Disabled", nil) return } if smart.StrTrimWhitespaces(jwtSignMethod) == "" { response.ContentBody = ApiResponseJsonERR(501, "Authentication JWT Algo is Empty or N/A", nil) return } if smart.AuthBearerIsEnabled() != true { response.ContentBody = ApiResponseJsonERR(423, "This URL is available just when the Auth Bearer is Enabled", nil) return } if (smart.StrTrimWhitespaces(authData.UserName) == "") || (smart.AuthIsValidUserName(authData.UserName) != true) { response.ContentBody = ApiResponseJsonERR(422, "Authentication UserName is Empty or Invalid: `"+authData.UserName+"`", nil) return } if (authData.SecurityKey == "") || (!smart.AuthIsValidSecurityKey(authData.SecurityKey)) { response.ContentBody = ApiResponseJsonERR(409, "Authenticated User`s SecurityKey is Unavailable or Invalid", nil) return } //-- var expMinutesStr string = smart.StrTrimWhitespaces(GetUrlQueryVar(r, "expMinutes")) if expMinutesStr == "" { expMinutesStr = smart.ConvertInt64ToStr(JwtDefaultExpirationMinutes) } var expMinutes int64 = 0 if (len(expMinutesStr) >= 1) && (len(expMinutesStr) <= 6) { expMinutes = smart.ParseStrAsInt64(expMinutesStr) } if expMinutes < JwtMinExpirationMinutes { expMinutes = JwtMinExpirationMinutes } else if expMinutes > JwtMaxExpirationMinutes { expMinutes = JwtMaxExpirationMinutes } //-- var ipAddress string = smart.StrTrimWhitespaces(GetUrlQueryVar(r, "ipAddress")) if len(ipAddress) > 255 { ipAddress = "" } if ipAddress == "" { ipAddress = "*" } var allowedIpList string = "*" var chkIp string = "*" if ipAddress != "*" { arrIps := smart.Explode(",", ipAddress) var safeIps []string = []string{} for i := 0; i < len(arrIps); i++ { arrIps[i] = smart.StrTrimWhitespaces(arrIps[i]) if (arrIps[i] != "") && (arrIps[i] != "*") { if smart.IsNetValidIpAddr(arrIps[i]) { if !smart.InListArr(arrIps[i], safeIps) { safeIps = append(safeIps, arrIps[i]) } } } if i >= 3 { break } } ipAddress = smart.StrTrimWhitespaces(smart.Implode(", ", safeIps)) if ipAddress == "" { ipAddress = "*" } for i := 0; i < len(safeIps); i++ { chkIp = safeIps[i] safeIps[i] = "<" + safeIps[i] + ">" } allowedIpList = smart.StrTrimWhitespaces(smart.Implode(",", safeIps)) if allowedIpList == "" { allowedIpList = "*" } if allowedIpList != "*" { errValidateAllowedIpList := smart.ValidateIPAddrList(allowedIpList) if errValidateAllowedIpList != nil { allowedIpList = "*" } } } data, errData := ApiAuthNewBearerTokenJwt(r, expMinutes, authData.UserName, authData.SecurityKey, allowedIpList, chkIp, "") if errData != nil { response.ContentBody = ApiResponseJsonERR(500, "JWT Token ERR: "+errData.Error(), nil) return } arrAccepts := GetClientMimeAcceptHeaders(r) acceptJson := smart.InListArr(smarthttputils.MIME_TYPE_JSON, arrAccepts) if acceptJson { response.ContentBody = ApiResponseJsonOK(data) response.ContentFileName = "auth-token.json" } else { var bwPath string = GetCurrentBrowserPath(r) var title = "JWT Access Token Generator" var headHtml string = assets.HTML_CSS_STYLE_PREFER_COLOR_DARK + "\n" var bodyHtml string = "<h1>" + smart.EscapeHtml(title) + "</h1>" + "\n" bodyHtml += `<hr>` + "\n" bodyHtml += `<div class="operation_hint">API access point <i class="sfi sfi-lock sfi-xl" title="Requires Authentication" style="cursor:help;"></i> [Accept: ` + smart.EscapeHtml(smarthttputils.MIME_TYPE_JSON) + `]: <i>` + smart.EscapeHtml("`"+bwPath+"`") + `</i> ; Query Parameters: (<i>?expMinutes=` + smart.ConvertInt64ToStr(JwtMinExpirationMinutes) + `..` + smart.ConvertInt64ToStr(JwtMaxExpirationMinutes) + `</i>)</div>` + "\n" bodyHtml += smart.RenderMarkersTpl(assets.ReadWebAsset("lib/tpl/syntax-highlight-init.inc.mtpl.htm"), map[string]string{ "THEME": "", "AREAS": "body", }) bodyHtml += smart.RenderMarkersTpl(assets.ReadWebAsset("lib/tpl/syntax-highlight-area.inc.mtpl.htm"), map[string]string{ "SYNTAX": "json", "CODE": smart.JsonNoErrChkEncode(data, true, false), }) bodyHtml += `<br>` + "\n" bodyHtml += `<b>LifeTime (minutes):</b> <input type="number" placeholder="1234" id="mins" maxlength="6" title="Min: ` + smart.EscapeHtml(smart.ConvertInt64ToStr(JwtMinExpirationMinutes)) + ` ; Max: ` + smart.EscapeHtml(smart.ConvertInt64ToStr(JwtMaxExpirationMinutes)) + ` ; Default: ` + smart.EscapeHtml(smart.ConvertInt64ToStr(JwtDefaultExpirationMinutes)) + ` " class="ux-field" value="` + smart.ConvertInt64ToStr(expMinutes) + `" min="` + smart.ConvertInt64ToStr(JwtMinExpirationMinutes) + `" max="` + smart.ConvertInt64ToStr(JwtMaxExpirationMinutes) + `" autocomplete="off" style="width:100px; text-align:center;">` + "\n" bodyHtml += `<b>IPAddresses (Ipv4/Ipv6):</b> <input type="text" id="ip" maxlength="255" placeholder="127.0.0.1, ::1" title="IP List separed by comma, or wildcard * for any IP" class="ux-field" value="` + ipAddress + `" autocomplete="off" style="width:200px; text-align:center;">` + "\n" bodyHtml += `<button class="ux-button ux-button-regular" onclick="let mins = smartJ$Utils.format_number_int(jQuery('input#mins').val(), false); if((!mins) || (!smartJ$Utils.isFiniteNumber(mins)) || (mins <= 0)) { smartJ$Browser.GrowlNotificationAdd('Error', '<h5>Invalid or Non-Numeric Expression</h5>', '', 3500, false, 'pink'); } else { let ipAddr = jQuery('input#ip').val(); setTimeout(() => { self.location = '` + smart.EscapeJs(bwPath) + `?expMinutes=' + smartJ$Utils.escape_url(mins) + '&ipAddress=' + smartJ$Utils.escape_url(ipAddr); }, 50); }"><i class="sfi sfi-lg sfi-spinner10"></i> Generate New JWT Access Token</button>` + "\n" bodyHtml += `<hr>` + "\n" bodyHtml += `<textarea id="area-secret" class="ux-field" style="min-width:700px; width:100%; height:50px; font-size:0.625rem !important; color:#CDCDCD !important;" readonly>` + smart.EscapeHtml(data.Token) + `</textarea>` + "\n" bodyHtml += `<br>` + "\n" bodyHtml += `<script>const copyElemToClipboard = () => { const err = smartJ$Browser.copyToClipboard('area-secret'); const txt = 'Copy to Clipboard'; const img = '<br><i class="sfi sfi-clipboard"></i>'; if(!!err) { console.error('ERR: copyElemToClipboard:', err); smartJ$Browser.GrowlNotificationAdd(txt, 'FAILED to Copy the Secret to Clipboard' + img, null, 3500, false, 'pink'); } else { smartJ$Browser.GrowlNotificationAdd(txt, 'Secret has been Copied to Clipboard' + img, null, 1500, false, 'blue'); } };</script>` + "\n" bodyHtml += `<button class="ux-button ux-button-small ux-button-details" onclick="copyElemToClipboard(); return false;"><i class="sfi sfi-stack"></i> Copy Secret to Clipboard</button>` + ` <span style="color:#685A8B;">[ username: ` + "`<b>" + smart.EscapeHtml(authData.UserName) + "</b>`" + ` ]</span>` + "\n" bodyHtml += `<br>` + "\n" response.ContentBody = srvassets.HtmlServerFaviconTemplate(title, headHtml, bodyHtml, true, assets.GetAuthLogo(false)) response.ContentFileName = "auth.html" } response.StatusCode = 200 return break default: } response.StatusCode = 404 response.LogMessage = "Invalid Auth Api Request Sub-Path" response.ContentBody = response.LogMessage response.ContentFileName = "404.html" return } //end fx
-- auth token api
var RouteHandlerFaviconStream HttpHandlerFunc = func(r *http.Request, headPath string, tailPaths []string, authData smart.AuthDataStruct) (response HttpResponse) { defer smart.PanicHandler() response.Headers = map[string]string{} response.Headers["Z-Content"] = "Streaming" //-- var fName string = "favicon.svg" var fPath string = WEB_PUBLIC_RELATIVE_ROOT_PATH + fName if !smart.PathIsFile(fPath) { response.StatusCode = 500 response.ContentBody = "Streaming File cannot be found: `" + fName + "`" response.ContentFileName = "500.html" return } response.ContentFileName = fName response.ContentStream = func() (ioReadStream io.Reader) { ioReadStream, fErr := os.Open(fPath) if fErr != nil { log.Println("[ERROR]", "Streaming Handler File `"+fPath+"` Open Error", fErr) return } return } return } //end fx
-- favicon (streaming) page (svg)
var RouteHandlerHomePage HttpHandlerFunc = func(r *http.Request, headPath string, tailPaths []string, authData smart.AuthDataStruct) (response HttpResponse) { defer smart.PanicHandler() response.StatusCode = 200 const title string = "WebApp" var headHtml string = assets.HTML_CSS_STYLE_PREFER_COLOR_DARK + "\n" + "<style>" + "\n" + "div.app { text-align:center; margin:20px; } div.app * { color: #ED2839 !important; }" + "\n" + "</style>" var bodyHtml string = `<center><div class="app" style="background:#FFFFFF; width:552px; border-radius:7px;">` + "<h1>" + smart.EscapeHtml(TheStrName) + "</h1>" + "\n" + `<img alt="app:svg" title="` + smart.EscapeHtml(title) + `" width="512" height="512" src="` + smart.EscapeHtml(assets.GetAppLogo(false)) + `"></div></center>` + "\n" response.ContentBody = assets.HtmlStandaloneFaviconTemplate(title, headHtml, bodyHtml, false, assets.GetAppLogo(true)) response.ContentFileName = "webapp.html" response.ContentDisposition = "" response.CacheExpiration = -1 response.CacheLastModified = "" response.CacheControl = smarthttputils.CACHE_CONTROL_NOCACHE response.Headers = nil response.Cookies = nil response.LogMessage = "" return } //end fx
-- home page (html)
var RouteHandlerInfoPage HttpHandlerFunc = func(r *http.Request, headPath string, tailPaths []string, authData smart.AuthDataStruct) (response HttpResponse) { defer smart.PanicHandler() remoteAddr, remotePort := GetVisitorRemoteIpAddrAndPort(r) _, realClientIp := GetVisitorRealIpAddr(r) basedom, dom, port, _ := GetBaseDomainDomainPort(r) var proxySetDetected string = smart.GetHttpProxyRealServerHostPortHeaderKey() // Proxy IP:port (if used) ; Go Server Port (may differ from the above external port) var proxyRealClientIp string = smart.GetHttpProxyRealClientIpHeaderKey() var proxyRealIpStatus string = "NoProxy" if proxyRealClientIp != "" { proxyRealIpStatus = "Proxy:" + proxyRealClientIp } signature := smart.GetHttpUserAgentFromRequest(r) bw, cls, os, mb := smart.GetUserAgentBrowserClassOs(signature) var isMobile string = "no" if mb == true { isMobile = "yes" } response.StatusCode = 208 const title string = "Service Info" var headHtml string = assets.HTML_META_ROBOTS_NOINDEX + "\n" + assets.HTML_CSS_STYLE_PREFER_COLOR_DARK var bodyHtml string = `<h1 style="display:inline-block;">` bodyHtml += smart.EscapeHtml(title) bodyHtml += `</h1>` bodyHtml += `<h4>` + smart.Nl2Br(smart.EscapeHtml(TheStrSignature)) + `</h4>` bodyHtml += `<hr>` bodyHtml += "Client Real-IP [" + smart.EscapeHtml(proxyRealIpStatus) + "] is: <b>`" + smart.EscapeHtml(realClientIp) + "`</b> ; Remote-IP (Host:Port) is: " + smart.EscapeHtml("`"+remoteAddr+"`:`"+remotePort+"`") + "<br>" bodyHtml += "Client UserAgent: <i>`" + smart.EscapeHtml(signature) + "`</i>" + "<br>" bodyHtml += `<div style="margin-top:4px; margin-bottom:12px;" title="` + smart.EscapeHtml("Client Browser Class: "+"`"+cls+"`"+" ; Client is Mobile: "+"`"+isMobile+"`") + `">` bodyHtml += "\n" bodyHtml += `<img src="` + smart.EscapeHtml(assets.GetClientBwLogo(bw, true)) + `" height="64" style="margin-right:12px; cursor:help;" alt="image-cli-bw" title="Client Browser: ` + smart.EscapeHtml("`"+bw+"`") + `">` bodyHtml += "\n" bodyHtml += `<img src="` + smart.EscapeHtml(assets.GetClientOSLogo(os, true)) + `" height="64" style="margin-right:12px; cursor:help;" alt="image-cli-os" title="Client OS: ` + smart.EscapeHtml("`"+os+"`") + `">` bodyHtml += "\n" bodyHtml += `</div>` bodyHtml += `<hr>` bodyHtml += "Server Proxy: <b>`" + smart.EscapeHtml(proxySetDetected) + "`</b>" + "<br>" bodyHtml += "Server Protocol: <b>`" + smart.EscapeHtml(smart.GetHttpProtocolFromRequest(r)) + "`</b>" + "<br>" bodyHtml += "Server BaseDomain: `" + smart.EscapeHtml(basedom) + "`" + "<br>" bodyHtml += "Server Domain: <b>`" + smart.EscapeHtml(dom) + "`</b>" + "<br>" bodyHtml += "Server Port: `" + smart.EscapeHtml(port) + "`" + "<br>" bodyHtml += "Server Base Path: <b>`" + smart.EscapeHtml(GetBaseBrowserPath()) + "`</b>" + " ; Internal Route Base Path: `" + smart.EscapeHtml(GetBasePath()) + "`" + "<br>" bodyHtml += "Server Path: <b>`" + smart.EscapeHtml(GetCurrentBrowserPath(r)) + "`</b>" + " ; Internal Route Path: `" + smart.EscapeHtml(GetCurrentPath(r)) + "`" + "<br>" bodyHtml += "Server QueryString: `" + smart.EscapeHtml(smart.GetHttpQueryStringFromRequest(r)) + "`" + "<br>" bodyHtml += `<div style="margin-top:4px; margin-bottom:12px;">` bodyHtml += "\n" bodyHtml += `<img src="` + smart.EscapeHtml(assets.GetProxyLogo(proxySetDetected, false)) + `" height="64" style="margin-right:12px; cursor:help;" alt="proxy-logo" title="Proxy: ` + smart.EscapeHtml("`"+proxySetDetected+"`") + `">` bodyHtml += "\n" bodyHtml += `<img src="` + smart.EscapeHtml(assets.GetSfLogo(false)) + `" height="64" style="margin-right:12px; cursor:help;" alt="sf-logo" title="Platform: ` + smart.EscapeHtml("`"+smart.NAME+" ("+smart.DESCRIPTION+") "+smart.VERSION+"`") + `">` bodyHtml += "\n" bodyHtml += `<img src="` + smart.EscapeHtml(assets.GetGolangLogo(false)) + `" height="64" style="margin-right:12px; cursor:help;" alt="golang-logo" title="Runtime: ` + smart.EscapeHtml("`"+smart.CurrentRuntimeVersion()+"`") + `">` bodyHtml += "\n" bodyHtml += `<img src="` + smart.EscapeHtml(assets.GetOSLogo(false)) + `" height="64" style="margin-right:12px; cursor:help;" alt="os-logo" title="OS / Arch: ` + smart.EscapeHtml("`"+smart.CurrentOSName()+"`"+" / "+"`"+smart.CurrentOSArch()+"`") + `">` bodyHtml += "\n" bodyHtml += `</div>` bodyHtml += `<hr>` bodyHtml += `<div style="font-size:0.75rem; color:#CCCCDD; text-align:right;">© 2023-` + smart.EscapeHtml(GetCurrentYear()) + ` unix-world.org</div>` response.ContentBody = assets.HtmlStandaloneTemplate(title, headHtml, bodyHtml, true) response.ContentFileName = "index.html" response.ContentDisposition = smarthttputils.DISP_TYPE_INLINE response.CacheExpiration = -1 response.CacheLastModified = "" response.CacheControl = smarthttputils.CACHE_CONTROL_NOCACHE response.Headers = map[string]string{} response.Headers["Z-Date-Time-UTC"] = smart.DateNowIsoUtc() response.Cookies = nil response.LogMessage = "" return } //end fx
-- info page (html)
var RouteHandlerStatusPage HttpHandlerFunc = func(r *http.Request, headPath string, tailPaths []string, authData smart.AuthDataStruct) (response HttpResponse) { defer smart.PanicHandler() response.StatusCode = 202 const title string = "Service Status: Up and Running ..." var headHtml string = assets.HTML_META_ROBOTS_NOINDEX + "\n" + assets.HTML_CSS_STYLE_PREFER_COLOR_DARK + "\n" + "<style>" + "\n" + "div.status { text-align:center; margin:10px; cursor:help; }" + "\n" + "div.signature { background:#778899; color:#FFFFFF; font-size:2rem; font-weight:bold; text-align:center; border-radius:3px; padding:10px; margin:20px; }" + "\n" + "</style>" var bodyHtml string = `<div class="status"><img alt="status:svg" title="` + smart.EscapeHtml(title) + `" width="48" height="48" src="` + smart.EscapeHtml(assets.GetSvgAsset("lib/framework/img/loading-spin.svg", false)) + `"></div>` + "\n" + `<div class="signature">` + "\n" + "<pre>" + "\n" + `<i class="sfi sfi-info"></i> ` + smart.EscapeHtml(TheStrSignature) + " ... is running" + "\n" + smart.EscapeHtml(smart.DateNowUtc()) + "</pre>" + "\n" + "</div>" response.ContentBody = srvassets.HtmlServerTemplate(title, headHtml, bodyHtml, false) response.ContentFileName = "status.html" response.Headers = map[string]string{} response.Headers["Refresh"] = "15" return } //end fx
-- status page (html)
var RouteHandlerVersionPage HttpHandlerFunc = func(r *http.Request, headPath string, tailPaths []string, authData smart.AuthDataStruct) (response HttpResponse) { defer smart.PanicHandler() response.StatusCode = 203 json := versionStruct{ Platform: "`" + smart.NAME + " (" + smart.DESCRIPTION + ") " + smart.VERSION + "`", Server: TheStrName, Version: VERSION, GoVersion: smart.CurrentRuntimeVersion(), OsName: smart.CurrentOSName(), OsArch: smart.CurrentOSArch(), Copyright: SIGNATURE, } response.ContentBody = smart.JsonNoErrChkEncode(json, false, false) response.ContentFileName = "version.json" return } //end fx
-- version page (json)
type HttpResponse ¶
type HttpResponse struct { StatusCode uint16 ContentStream smarthttputils.HttpStreamerFunc ContentBody string ContentFileName string ContentDisposition string CacheExpiration int CacheLastModified string CacheControl string Headers map[string]string Cookies []smarthttputils.CookieData LogMessage string }
type JwtAudience ¶
type JwtAudience struct { Error error IpList string Area string Privileges string Restrictions string Xtras string }
func JwtParseAudience ¶
func JwtParseAudience(audience []string) JwtAudience
type JwtClaims ¶
type JwtClaims struct { Username string `json:"usr"` jwt.RegisteredClaims }
type JwtData ¶
type JwtData struct { Type string `json:"type"` Size uint64 `json:"size"` Token string `json:"token"` TimeNow int64 `json:"timeNow"` ExpMinutes int64 `json:"expMinutes"` ExpAt string `json:"expAt"` PublicKey string `json:"publicKey,omitempty"` MetaInfo JwtClaims `json:"metaInfo"` }
type JwtTokenData ¶
type JwtTokenData struct { Error error `json:"error,omitempty"` Type string `json:"-"` Algo string `json:"algo"` ID string `json:"serial,omitempty"` Issuer string `json:"issuer,omitempty"` Created string `json:"created,omitempty"` ICreated int64 `json:"-"` IExpires int64 `json:"-"` Expires string `json:"expires,omitempty"` UserName string `json:"userName"` Audience []string `json:"audience"` }
func JwtExtractData ¶
func JwtExtractData(tokenString string) JwtTokenData
type MultiPartPostFile ¶
type MultiPartPostFile struct { Error error Header *multipart.FileHeader File multipart.File }
func GetPostFile ¶
func GetPostFile(r *http.Request, key string) MultiPartPostFile
func GetPostFiles ¶
func GetPostFiles(r *http.Request, key string) ([]MultiPartPostFile, error)
type WebAuthAccountsMethods ¶
type WebAuthAccountsMethods struct { GetAuthUserRecordByUserName WebAuthGetAuthUserRecordByUserName ValidateAuthUserByTokenRecord WebAuthValidateAuthUserByTokenRecord }
func GetWebAuthAccountsMethods ¶
func GetWebAuthAccountsMethods() WebAuthAccountsMethods
type WebAuthGetAuthUserRecordByUserName ¶
type WebAuthGetAuthUserRecordByUserName func(user string) (error, smart.AuthUserRecord)
type WebAuthValidateAuthUserByTokenRecord ¶
type WebAuthValidateAuthUserByTokenRecord func(user string, token string) (error, smart.AuthUserToken)