Domain Lock HTML5 games

#1

This is a basic module to help you lock HTML5 games to a specific domain (or a list of domains). This can be required for licensing for example, to keep other sites from hosting your games without your permission that easily. Most web browsers do not allow spoofing location.hostname for security reasons so it’s pretty safe to use for checking current domain.

You’ll want to provide a helpful message in case of domain check failure. When not running on HTML5 builds the default behavior is to always pass a domain check.

You will want to add every version of your domain if your games can be hosted on them. Such as www.yougamesdomain.com and yougamesdmain.com both. You’ll probably want to add “localhost” while debugging. You can use sys.get_engine_info to check if the engine is running in debug or not.

If you make improvements please post them here! For example, allowing wildcard for subdomains as an option to enable would be nice.

local domainlock = require("utils.domainlock")
domainlock.add_domain("localhost")
domainlock.add_domain("defold.com")
domainlock.add_domain("www.defold.com")
if domainlock.verify_domain() then print("hurray! we're on a verified domain!") end

domainlock.lua (457 Bytes)

local M = {}

M.domains = {}

function M.add_domain(domain)
	table.insert(M.domains, domain)
end

function M.verify_domain()
	if not html5 then return true end
	local current_domain = html5.run("location.hostname")
	for key, value in ipairs(M.domains) do
		if value == current_domain then
			return true
		end
	end
	return false
end

function M.get_current_domain()
	if html5 then
		return html5.run("location.hostname")
	else
		return ""
	end
end

return M
14 Likes

#2

Does this still work? I’ve faced a weird behaviour after updating Editor to 1.2.157 (and maybe even to 1.2.156) where string.find() stopped working as expected.

Try this:
local domain = "anvil-games.com"
print (string.find(domain, domain))
It prints nil for me, what means, it can’t find the string in itself. It worked before and I can’t understand why it stopped working.

BTW this string.find(domain, domain, 1, true) works. Docs

Any ideas?

0 Likes

#3

string.find searches for patterns, not strings (read the chapter on patterns from Programming In Lua). The minus symbol in a pattern means zero or more repetitions of the previous character. Passing true as the fourth argument interprets the second arg as a plain string, not a pattern, which is what you want.

See https://www.lua.org/manual/5.1/manual.html#5.4

2 Likes

#4

string.find() isn’t used in this module? The source is posted above, it just checks against location.hostname

Subdomain matters. So if you want www.anvil-games.com to work you need to add that variant too.

2 Likes

#5

Oops, sorry, my bad, I’ve made a little change to your code to search for substring instead of precisely comparing strings. I need this because for one of publishers there is generated subdomain in the game url like „randomname12345.yandex.net” and I cannot hardcode it, but „yandex.net” part is always the same, so I,ve used string.find()

1 Like

#6

You should have posted your changes! :smiley:

I’m not sure of best way to support wildcard domains in the module.

Maybe if a star is detected at the start of the domain like “*. domain.com” then compare the test domain against the test domain and a version with a split off the first subdomain. You don’t always want wildcard domain behavior enabled for everyone by default though because some common cdn hosting platforms are used by some people.

1 Like

#7

Here’s a test, but there is probably a more elegant way to do it…

local M = {}

M.domains = {}

local function split(s, delimiter)
	delimiter = delimiter or '%s'
	local t={}
	local i=1
	for str in string.gmatch(s, '([^'..delimiter..']+)') do
		t[i] = str
		i = i + 1
	end
	return t
end

local function join(t, delimiter)
	delimiter = delimiter or ' '
	return table.concat(t, delimiter)	
end

function M.add_domain(domain)
	table.insert(M.domains, domain)
end

function M.verify_domain()
	if not html5 then return true end
	local current_domain = html5.run("location.hostname")
	for key, value in ipairs(M.domains) do
    -- check if added domain is wildcard
    local first = string.sub(value, 1, 1)
    if first == "*" then
      local actual_domain = string.sub(value, 3, #value)
      local actual_current_domain = split(current_domain, ".")
      table.remove(actual_current_domain, 1)
      actual_current_domain = join(actual_current_domain, ".")
      if actual_domain == actual_current_domain or actual_domain == current_domain then
        return true
      end
    else
      if value == current_domain then
        return true
      end    
    end
	end
	return false
end

function M.get_current_domain()
	if html5 then
		return html5.run("location.hostname")
	else
		return ""
	end
end

By the way, not like this is a problem you would ever have someone figure out, but the string.find method would be vulnerable to sub domain based “attack” hosting so if you had randomname12345.yandex.net as your lock domain someone could host the game on randomname12345.yandex.net.evildomain.com and it would work, I think.

1 Like

#8

Lua pattern matching should be powerful enough to solve most kinds of comparisons needed, including a random portion in a domain.

1 Like

#9

yes, I thought about it too, possible solution - break domain by “.” symbol and check from end, like:

but i’m not sure if I’ll implement this, the whole check was just as a foolproof from very basic “hack”

UPD: or just simply check if substring with the length of the pattern exists in the end of domain you’re checking.

1 Like

#10

The modified version I posted above is that more of less. So with it you would add “*.yandex.net” as a wildcard domain and it would work with any subdomain but not on any other domain not added.

@britzl How would you set the wildcard domain “*.example.com” still?

2 Likes

#11

I’m thinking something like this:

local function verify(domain, accepted_domains)
	for _,accepted in ipairs(accepted_domains) do
		if string.find(domain, accepted) then
			return true
		end
	end
	return false
end


local function test(domain, domains)
	print(domain, verify(domain, domains))
end

local domains = {
	"^localhost$",
	".*%.defold%.com$",
}

test("www.defold.com", domains)
test("foo.defold.com", domains)
test("foo.defold.com.evildomain.com", domains)
test("localhost", domains)
test("localhosty", domains)

Some results:

$ lua domainlock.lua 
www.defold.com true
foo.defold.com true
foo.defold.com.evildomain.com false
localhost true
localhosty false
6 Likes