Problems Debugging iOS Safari from Mac
After setting up the proper permissions, running the Mac Safari debugger on a tab
running in iOS Safari is as easy as navigating to the Develop
menu and
clicking the iOS device and selecting the tab you want to inspect.
Anyways, when loading up a project running on your Mac you might hit this error:
Invalid Host header
Now if you dig around long enough you’ll find out that the webpack dev server is sending this response:
app.all('*', (req, res, next) => {
if (this.checkHost(req.headers)) {
return next();
}
res.send('Invalid Host header');
});
So we need to update checkHost
to allow iOS Safari to access the field.
We can use the disableHostCheck
option, which disables the check entirely, a
little risky, or add the iOS device to the allowedHosts
, easy enough.
diff --git a/frontend/scripts/start.js b/frontend/scripts/start.js
index 0159f5a..1937917 100644
--- a/frontend/scripts/start.js
+++ b/frontend/scripts/start.js
@@ -317,6 +317,7 @@ function runDevServer(host, port, protocol) {
https: protocol === "https",
host,
+ allowedHosts: ["otter.local"]
})
// Our custom middleware proxies requests to /index.html or a remote API.
After a restart of the frontend server, iOS Safari can now load the page.
Then when trying to login, the django backend throws an error:
[10/Apr/2022 02:43:57] "POST /api/v1/auth/login/ HTTP/1.1" 500 169800
level=INFO msg="Login by user@example.com" user_id=1 request_id=7dbdf460-986a-47f1-84f6-756377e2c7e7 name=core.auth.views pathname="/backend/core/auth/views.py" lineno=69 funcname=post process=22783 thread=123145578180608
Internal Server Error: /api/v1/auth/login/
Traceback (most recent call last):
File "/backend/.venv/lib/python3.7/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
psycopg2.errors.InvalidTextRepresentation: invalid input syntax for type inet: "fe80::1c0d:c874:ae30:74ae%en0"
LINE 1: ...04-10T03:11:20.316332+00:00'::timestamptz, "ip" = 'fe80::1c0...
^
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/backend/.venv/lib/python3.7/site-packages/django/core/handlers/exception.py", line 47, in inner
response = get_response(request)
File "/backend/.venv/lib/python3.7/site-packages/sentry_sdk/integrations/django/middleware.py", line 175, in __call__
return f(*args, **kwargs)
File "/backend/.venv/lib/python3.7/site-packages/django/utils/deprecation.py", line 119, in __call__
response = self.process_response(request, response)
File "/backend/.venv/lib/python3.7/site-packages/django/contrib/sessions/middleware.py", line 61, in process_response
request.session.save()
File "/backend/.venv/lib/python3.7/site-packages/user_sessions/backends/db.py", line 83, in save
obj.save(force_insert=must_create, using=using)
File "/backend/.venv/lib/python3.7/site-packages/django/db/models/base.py", line 727, in save
force_update=force_update, update_fields=update_fields)
File "/backend/.venv/lib/python3.7/site-packages/django/db/models/base.py", line 765, in save_base
force_update, using, update_fields,
File "/backend/.venv/lib/python3.7/site-packages/django/db/models/base.py", line 846, in _save_table
forced_update)
File "/backend/.venv/lib/python3.7/site-packages/django/db/models/base.py", line 899, in _do_update
return filtered._update(values) > 0
File "/backend/.venv/lib/python3.7/site-packages/django/db/models/query.py", line 802, in _update
return query.get_compiler(self.db).execute_sql(CURSOR)
File "/backend/.venv/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 1559, in execute_sql
cursor = super().execute_sql(result_type)
File "/backend/.venv/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 1175, in execute_sql
cursor.execute(sql, params)
File "/backend/.venv/lib/python3.7/site-packages/django/db/backends/utils.py", line 98, in execute
return super().execute(sql, params)
File "/backend/.venv/lib/python3.7/site-packages/sentry_sdk/integrations/django/__init__.py", line 508, in execute
return real_execute(self, sql, params)
File "/backend/.venv/lib/python3.7/site-packages/django/db/backends/utils.py", line 66, in execute
return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
File "/backend/.venv/lib/python3.7/site-packages/django/db/backends/utils.py", line 75, in _execute_with_wrappers
return executor(sql, params, many, context)
File "/backend/.venv/lib/python3.7/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
File "/backend/.venv/lib/python3.7/site-packages/django/db/utils.py", line 90, in __exit__
raise dj_exc_value.with_traceback(traceback) from exc_value
File "/backend/.venv/lib/python3.7/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
django.db.utils.DataError: invalid input syntax for type inet: "fe80::1c0d:c874:ae30:74ae%en0"
LINE 1: ...04-10T03:11:20.316332+00:00'::timestamptz, "ip" = 'fe80::1c0...
^
level=ERROR msg="Internal Server Error: /api/v1/auth/login/" user_id=1 request_id=7dbdf460-986a-47f1-84f6-756377e2c7e7 name=django.request pathname="/backend/.venv/lib/python3.7/site-packages/django/utils/log.py" lineno=230 funcname=log_response process=22783 thread=123145578180608
Traceback (most recent call last):
File "/backend/.venv/lib/python3.7/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
psycopg2.errors.InvalidTextRepresentation: invalid input syntax for type inet: "fe80::1c0d:c874:ae30:74ae%en0"
LINE 1: ...04-10T03:11:20.316332+00:00'::timestamptz, "ip" = 'fe80::1c0...
^
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/backend/.venv/lib/python3.7/site-packages/django/core/handlers/exception.py", line 47, in inner
response = get_response(request)
File "/backend/.venv/lib/python3.7/site-packages/sentry_sdk/integrations/django/middleware.py", line 175, in __call__
return f(*args, **kwargs)
File "/backend/.venv/lib/python3.7/site-packages/django/utils/deprecation.py", line 119, in __call__
response = self.process_response(request, response)
File "/backend/.venv/lib/python3.7/site-packages/django/contrib/sessions/middleware.py", line 61, in process_response
request.session.save()
File "/backend/.venv/lib/python3.7/site-packages/user_sessions/backends/db.py", line 83, in save
obj.save(force_insert=must_create, using=using)
File "/backend/.venv/lib/python3.7/site-packages/django/db/models/base.py", line 727, in save
force_update=force_update, update_fields=update_fields)
File "/backend/.venv/lib/python3.7/site-packages/django/db/models/base.py", line 765, in save_base
force_update, using, update_fields,
File "/backend/.venv/lib/python3.7/site-packages/django/db/models/base.py", line 846, in _save_table
forced_update)
File "/backend/.venv/lib/python3.7/site-packages/django/db/models/base.py", line 899, in _do_update
return filtered._update(values) > 0
File "/backend/.venv/lib/python3.7/site-packages/django/db/models/query.py", line 802, in _update
return query.get_compiler(self.db).execute_sql(CURSOR)
File "/backend/.venv/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 1559, in execute_sql
cursor = super().execute_sql(result_type)
File "/backend/.venv/lib/python3.7/site-packages/django/db/models/sql/compiler.py", line 1175, in execute_sql
cursor.execute(sql, params)
File "/backend/.venv/lib/python3.7/site-packages/django/db/backends/utils.py", line 98, in execute
return super().execute(sql, params)
File "/backend/.venv/lib/python3.7/site-packages/sentry_sdk/integrations/django/__init__.py", line 508, in execute
return real_execute(self, sql, params)
File "/backend/.venv/lib/python3.7/site-packages/django/db/backends/utils.py", line 66, in execute
return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
File "/backend/.venv/lib/python3.7/site-packages/django/db/backends/utils.py", line 75, in _execute_with_wrappers
return executor(sql, params, many, context)
File "/backend/.venv/lib/python3.7/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
File "/backend/.venv/lib/python3.7/site-packages/django/db/utils.py", line 90, in __exit__
raise dj_exc_value.with_traceback(traceback) from exc_value
File "/backend/.venv/lib/python3.7/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
django.db.utils.DataError: invalid input syntax for type inet: "fe80::1c0d:c874:ae30:74ae%en0"
LINE 1: ...04-10T03:11:20.316332+00:00'::timestamptz, "ip" = 'fe80::1c0...
Tailing the postgres logs after enabling logging of all statements gives us:
tail -f "~/Library/Application Support/Postgres/var-10/pg_log/postgresql.log"
2022-04-09 23:11:20.318 EDT [2545] LOG: statement: BEGIN
2022-04-09 23:11:20.319 EDT [2545] LOG: statement: UPDATE "user_sessions_session" SET "session_data" = '.eJxVjMsOwiAQRf-FtSHA8HTp3m8gAwNSNTQp7cr479qkC93ec859sYjb2uI2yhInYmcm2el3S5gfpe-A7thvM89zX5cp8V3hBx38OlN5Xg7376DhaN_agHMVg85KAXifEwV0WCwJbcEQhEQyC6e9yN6KqkK1psoAporknCH2_gDSSjdX:1ndNyy:5IK9ioBIcecKt0URwDpT1t9iM-m361rZOwwq9Bw5e0M', "expire_date" = '2023-04-10T03:11:20.315644+00:00'::timestamptz, "user_id" = 1, "user_agent" = 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1', "last_activity" = '2022-04-10T03:11:20.316332+00:00'::timestamptz, "ip" = 'fe80::1c0d:c874:ae30:74ae%en0'::inet WHERE "user_sessions_session"."session_key" = 'wjtnphxcc04rc5ddi9a82u6jegjisz8o'
2022-04-09 23:11:20.320 EDT [2545] ERROR: invalid input syntax for type inet: "fe80::1c0d:c874:ae30:74ae%en0" at character 595
2022-04-09 23:11:20.320 EDT [2545] STATEMENT: UPDATE "user_sessions_session" SET "session_data" = '.eJxVjMsOwiAQRf-FtSHA8HTp3m8gAwNSNTQp7cr479qkC93ec859sYjb2uI2yhInYmcm2el3S5gfpe-A7thvM89zX5cp8V3hBx38OlN5Xg7376DhaN_agHMVg85KAXifEwV0WCwJbcEQhEQyC6e9yN6KqkK1psoAporknCH2_gDSSjdX:1ndNyy:5IK9ioBIcecKt0URwDpT1t9iM-m361rZOwwq9Bw5e0M', "expire_date" = '2023-04-10T03:11:20.315644+00:00'::timestamptz, "user_id" = 1, "user_agent" = 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1', "last_activity" = '2022-04-10T03:11:20.316332+00:00'::timestamptz, "ip" = 'fe80::1c0d:c874:ae30:74ae%en0'::inet WHERE "user_sessions_session"."session_key" = 'wjtnphxcc04rc5ddi9a82u6jegjisz8o'
2022-04-09 23:11:20.322 EDT [2545] LOG: statement: ROLLBACK
If we run the query in question through a formatter we get:
UPDATE
"user_sessions_session"
SET
"session_data" = '.eJxVjMsOwiAQRf-FtSHA8HTp3m8gAwNSNTQp7cr479qkC93ec859sYjb2uI2yhInYmcm2el3S5gfpe-A7thvM89zX5cp8V3hBx38OlN5Xg7376DhaN_agHMVg85KAXifEwV0WCwJbcEQhEQyC6e9yN6KqkK1psoAporknCH2_gDSSjdX:1ndNyy:5IK9ioBIcecKt0URwDpT1t9iM-m361rZOwwq9Bw5e0M',
"expire_date" = '2023-04-10T03:11:20.315644+00:00'::timestamptz,
"user_id" = 1,
"user_agent" = 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1',
"last_activity" = '2022-04-10T03:11:20.316332+00:00'::timestamptz,
"ip" = 'fe80::1c0d:c874:ae30:74ae%en0'::inet
WHERE
"user_sessions_session"."session_key" = 'wjtnphxcc04rc5ddi9a82u6jegjisz8o'
And if we look at the error, postgres is complaining about the ip being malformed:
select 'fe80::1c0d:c874:ae30:74ae%en0'::inet
gives us the same error:
Query 1 ERROR: ERROR: invalid input syntax for type inet: "fe80::1c0d:c874:ae30:74ae%en0"
LINE 1: select 'fe80::1c0d:c874:ae30:74ae%en0'::inet;
Clearly %en0
at the end of the ip shouldn’t be there.
Messing around in pdb
, we can see the WSGI headers Django is using:
{
"Content-Length": "100",
"Content-Type": "application/json;charset=utf-8",
"X-Forwarded-Host": "otter.local:3000",
"X-Forwarded-Proto": "http",
"X-Forwarded-Port": "3000",
"X-Forwarded-For": "fe80::1c0d:c874:ae30:74ae%en0",
"Cookie": "sessionid=wjtnphxcc04rc5ddi9a82u6jegjisz8o",
"Referer": "http://otter.local:3000/login",
"Connection": "close",
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Mobile/15E148 Safari/604.1",
"Origin": "http://localhost:8000",
"X-Request-Id": "ee972eaa-1f59-41c6-8473-b4bf7d898517",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "en-US,en;q=0.9",
"Accept": "application/json, text/plain, */*",
"Host": "localhost:8000",
}
So the weird IP is originating further upstream, checking the network devices:
ifconfig
we can see that en0
is setup with the ip in question:
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
options=400<CHANNEL_IO>
ether ac:bc:32:7b:0f:67
inet6 fe80::97:2a3d:d7b4:85ac%en0 prefixlen 64 secured scopeid 0x4
inet 192.168.99.26 netmask 0xffffff00 broadcast 192.168.99.255
nd6 options=201<PERFORMNUD,DAD>
media: autoselect
status: active
Looking in man ifconfig
, prefixlen
is normal:
prefixlen len
(Inet6 only.) Specify that len bits are reserved for subdividing networks into
sub-networks. The len must be integer, and for syntactical reason it must be
between 0 to 128. It is almost always 64 under the current IPv6 assignment
rule. If the parameter is omitted, 64 is used.
The prefix can also be specified using the slash notation after the address.
See the address option above for more information.
scopeid
is no where to be found in the man
page, but looks suspect with 0x4
being the length of %en0
.
A little googling, and we end up at
https://networkengineering.stackexchange.com/a/46657/43747 which describes the
%en0
stuff at the end of the ip as link local addressing and points to the
https://www.rfc-editor.org/rfc/rfc6874 (2013) which itself references
https://www.rfc-editor.org/rfc/rfc4007 (2005) which defines the IPv6 Scoped
Addresses syntax:
11. Textual Representation
As already mentioned, to specify an IPv6 non-global address without
ambiguity, an intended scope zone should be specified as well. As a
common notation to specify the scope zone, an implementation SHOULD
support the following format:
<address>%<zone_id>
where
<address> is a literal IPv6 address,
<zone_id> is a string identifying the zone of the address, and
`%' is a delimiter character to distinguish between <address> and
<zone_id>.
So, when connecting to an iOS device, the Mac uses an IPv6 link local IP address with a zone id.
The Fix
Django sessions don’t include an ip address field, but this project uses a third party library for sessions that does. With a quick hack, we can get things working again:
diff --git a/user_sessions/backends/db.py b/user_sessions/backends/db.py
index 4c02f2c..db4af6d 100644
--- a/user_sessions/backends/db.py
+++ b/user_sessions/backends/db.py
@@ -16,6 +16,8 @@ class SessionStore(SessionBase):
super(SessionStore, self).__init__(session_key)
# Truncate user_agent string to max_length of the CharField
self.user_agent = user_agent[:200] if user_agent else user_agent
+ if ip.endswith("%en0"):
+ ip = ip[:len(ip) - len("%en0")]
self.ip = ip
self.user_id = None