authdrift 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- authdrift-0.1.0/.gitignore +13 -0
- authdrift-0.1.0/LICENSE +21 -0
- authdrift-0.1.0/PKG-INFO +88 -0
- authdrift-0.1.0/README.md +66 -0
- authdrift-0.1.0/pyproject.toml +53 -0
- authdrift-0.1.0/src/authdrift/__init__.py +3 -0
- authdrift-0.1.0/src/authdrift/cli.py +89 -0
- authdrift-0.1.0/src/authdrift/rules/oauth-email-key.yaml +73 -0
- authdrift-0.1.0/tests/fixtures/nextauth-vulnerable.ts +24 -0
- authdrift-0.1.0/tests/fixtures/passport-safe.js +20 -0
- authdrift-0.1.0/tests/fixtures/passport-vulnerable.js +15 -0
- authdrift-0.1.0/tests/fixtures/python-vulnerable.py +25 -0
authdrift-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Srinathprasanna Neelagiri Chettiyar Shanmugam
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
authdrift-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: authdrift
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Find OAuth handlers that will break when users rename their identifier.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Neelagiri65/authdrift
|
|
6
|
+
Project-URL: Issues, https://github.com/Neelagiri65/authdrift/issues
|
|
7
|
+
Author: Neelagiri
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: gmail,identity,oauth,oidc,phantom-user,sast,security,semgrep
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: System Administrators
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Topic :: Security
|
|
18
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Requires-Dist: semgrep>=1.50.0
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# authdrift
|
|
24
|
+
|
|
25
|
+
> Find OAuth handlers that will break when users rename their Gmail.
|
|
26
|
+
|
|
27
|
+
On 31 March 2026, Google began letting users rename their primary Gmail address. Every OAuth integration that uses email as the user lookup key will silently create a duplicate account when a user renames. Most do.
|
|
28
|
+
|
|
29
|
+
`authdrift` is a static analysis tool that scans your codebase for the exact patterns that explode on a Gmail rename — and on the ~0.04% baseline `sub`-claim drift Truffle Security documented in January 2025.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install authdrift
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Requires `semgrep` (installed automatically).
|
|
38
|
+
|
|
39
|
+
## Use
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
authdrift scan ./
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Example output:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
src/auth/google.ts
|
|
49
|
+
12:18 WARNING oauth-passport-email-as-primary-key
|
|
50
|
+
This OAuth handler is using `profile.emails[0].value` as a user lookup key.
|
|
51
|
+
When a user renames their Gmail address, this lookup will fail and your
|
|
52
|
+
application will silently create a duplicate user record.
|
|
53
|
+
Fix: use `profile.id` (the OIDC `sub` claim) as the immutable primary key.
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
JSON output for CI:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
authdrift scan ./ --json
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Why this matters
|
|
63
|
+
|
|
64
|
+
Atlassian's own KB confirms that SCIM email changes create duplicate Atlassian Cloud accounts. Google's developer documentation acknowledges that revoke + reauth produces duplicate accounts. The cascade is documented; the fix is not deployed.
|
|
65
|
+
|
|
66
|
+
`authdrift` flags the exact code patterns that produce phantom users after a rename event. Three rules, narrowly scoped, designed to slot into CI alongside `semgrep`, `trufflehog`, and `gitleaks` without producing noise.
|
|
67
|
+
|
|
68
|
+
## What it catches today
|
|
69
|
+
|
|
70
|
+
- **Passport.js** handlers using `profile.emails[0].value` as a user lookup key (`passport-google-oauth20`, `passport-google-oauth2`)
|
|
71
|
+
- **NextAuth** `signIn` callbacks resolving users by `user.email` against Prisma
|
|
72
|
+
- **Python** (Django / SQLAlchemy) handlers querying by `userinfo['email']` from Google's userinfo response
|
|
73
|
+
|
|
74
|
+
## What it deliberately does NOT do
|
|
75
|
+
|
|
76
|
+
- Flag every reference to `email` in an OAuth file. The rules require the email value to be used as a lookup key, not just read or logged.
|
|
77
|
+
- Flag handlers that key on `sub` / `profile.id` and use email only as a contact attribute.
|
|
78
|
+
- Auto-fix anything. Read-only by design.
|
|
79
|
+
|
|
80
|
+
## Roadmap
|
|
81
|
+
|
|
82
|
+
- More OAuth library coverage (lucia-auth, authlib, omniauth, Clerk, Supabase Auth, Firebase Auth)
|
|
83
|
+
- A `--fix` mode that suggests the `sub`-keyed equivalent
|
|
84
|
+
- A hosted scan for organisations that can't run a CLI
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# authdrift
|
|
2
|
+
|
|
3
|
+
> Find OAuth handlers that will break when users rename their Gmail.
|
|
4
|
+
|
|
5
|
+
On 31 March 2026, Google began letting users rename their primary Gmail address. Every OAuth integration that uses email as the user lookup key will silently create a duplicate account when a user renames. Most do.
|
|
6
|
+
|
|
7
|
+
`authdrift` is a static analysis tool that scans your codebase for the exact patterns that explode on a Gmail rename — and on the ~0.04% baseline `sub`-claim drift Truffle Security documented in January 2025.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install authdrift
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Requires `semgrep` (installed automatically).
|
|
16
|
+
|
|
17
|
+
## Use
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
authdrift scan ./
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Example output:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
src/auth/google.ts
|
|
27
|
+
12:18 WARNING oauth-passport-email-as-primary-key
|
|
28
|
+
This OAuth handler is using `profile.emails[0].value` as a user lookup key.
|
|
29
|
+
When a user renames their Gmail address, this lookup will fail and your
|
|
30
|
+
application will silently create a duplicate user record.
|
|
31
|
+
Fix: use `profile.id` (the OIDC `sub` claim) as the immutable primary key.
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
JSON output for CI:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
authdrift scan ./ --json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Why this matters
|
|
41
|
+
|
|
42
|
+
Atlassian's own KB confirms that SCIM email changes create duplicate Atlassian Cloud accounts. Google's developer documentation acknowledges that revoke + reauth produces duplicate accounts. The cascade is documented; the fix is not deployed.
|
|
43
|
+
|
|
44
|
+
`authdrift` flags the exact code patterns that produce phantom users after a rename event. Three rules, narrowly scoped, designed to slot into CI alongside `semgrep`, `trufflehog`, and `gitleaks` without producing noise.
|
|
45
|
+
|
|
46
|
+
## What it catches today
|
|
47
|
+
|
|
48
|
+
- **Passport.js** handlers using `profile.emails[0].value` as a user lookup key (`passport-google-oauth20`, `passport-google-oauth2`)
|
|
49
|
+
- **NextAuth** `signIn` callbacks resolving users by `user.email` against Prisma
|
|
50
|
+
- **Python** (Django / SQLAlchemy) handlers querying by `userinfo['email']` from Google's userinfo response
|
|
51
|
+
|
|
52
|
+
## What it deliberately does NOT do
|
|
53
|
+
|
|
54
|
+
- Flag every reference to `email` in an OAuth file. The rules require the email value to be used as a lookup key, not just read or logged.
|
|
55
|
+
- Flag handlers that key on `sub` / `profile.id` and use email only as a contact attribute.
|
|
56
|
+
- Auto-fix anything. Read-only by design.
|
|
57
|
+
|
|
58
|
+
## Roadmap
|
|
59
|
+
|
|
60
|
+
- More OAuth library coverage (lucia-auth, authlib, omniauth, Clerk, Supabase Auth, Firebase Auth)
|
|
61
|
+
- A `--fix` mode that suggests the `sub`-keyed equivalent
|
|
62
|
+
- A hosted scan for organisations that can't run a CLI
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
MIT.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "authdrift"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Find OAuth handlers that will break when users rename their identifier."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Neelagiri" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"oauth",
|
|
15
|
+
"oidc",
|
|
16
|
+
"security",
|
|
17
|
+
"sast",
|
|
18
|
+
"semgrep",
|
|
19
|
+
"identity",
|
|
20
|
+
"gmail",
|
|
21
|
+
"phantom-user",
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 3 - Alpha",
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
|
+
"Intended Audience :: System Administrators",
|
|
27
|
+
"Topic :: Security",
|
|
28
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
29
|
+
"License :: OSI Approved :: MIT License",
|
|
30
|
+
"Programming Language :: Python :: 3",
|
|
31
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
32
|
+
]
|
|
33
|
+
dependencies = [
|
|
34
|
+
"semgrep>=1.50.0",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
authdrift = "authdrift.cli:main"
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://github.com/Neelagiri65/authdrift"
|
|
42
|
+
Issues = "https://github.com/Neelagiri65/authdrift/issues"
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.targets.wheel]
|
|
45
|
+
packages = ["src/authdrift"]
|
|
46
|
+
|
|
47
|
+
[tool.hatch.build.targets.sdist]
|
|
48
|
+
include = [
|
|
49
|
+
"src/authdrift",
|
|
50
|
+
"README.md",
|
|
51
|
+
"LICENSE",
|
|
52
|
+
"tests",
|
|
53
|
+
]
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""authdrift CLI — wraps semgrep with the bundled authdrift ruleset."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from importlib.resources import as_file, files
|
|
11
|
+
|
|
12
|
+
from . import __version__
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _rules_path() -> str:
|
|
16
|
+
"""Return the on-disk path to the bundled rules directory."""
|
|
17
|
+
rules = files("authdrift").joinpath("rules")
|
|
18
|
+
with as_file(rules) as p:
|
|
19
|
+
return str(p)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _find_semgrep() -> str | None:
|
|
23
|
+
"""Locate the semgrep executable.
|
|
24
|
+
|
|
25
|
+
Prefer the binary adjacent to the current Python interpreter so that
|
|
26
|
+
a venv-installed authdrift uses its venv-installed semgrep — even when
|
|
27
|
+
that venv is not active on PATH.
|
|
28
|
+
"""
|
|
29
|
+
bin_dir = os.path.dirname(sys.executable)
|
|
30
|
+
candidate = os.path.join(bin_dir, "semgrep")
|
|
31
|
+
if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
|
|
32
|
+
return candidate
|
|
33
|
+
return shutil.which("semgrep")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _scan(path: str, json_output: bool) -> int:
|
|
37
|
+
semgrep_bin = _find_semgrep()
|
|
38
|
+
if semgrep_bin is None:
|
|
39
|
+
sys.stderr.write(
|
|
40
|
+
"authdrift: semgrep is required but was not found.\n"
|
|
41
|
+
"Install it with: pip install semgrep\n"
|
|
42
|
+
)
|
|
43
|
+
return 2
|
|
44
|
+
|
|
45
|
+
cmd = [semgrep_bin, "scan", "--config", _rules_path(), "--error", path]
|
|
46
|
+
if json_output:
|
|
47
|
+
cmd.append("--json")
|
|
48
|
+
return subprocess.call(cmd)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def main(argv: list[str] | None = None) -> int:
|
|
52
|
+
parser = argparse.ArgumentParser(
|
|
53
|
+
prog="authdrift",
|
|
54
|
+
description=(
|
|
55
|
+
"Find OAuth handlers that will break when users rename their "
|
|
56
|
+
"identifier (e.g. a Gmail rename)."
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
parser.add_argument(
|
|
60
|
+
"--version",
|
|
61
|
+
action="version",
|
|
62
|
+
version=f"authdrift {__version__}",
|
|
63
|
+
)
|
|
64
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
65
|
+
|
|
66
|
+
scan = sub.add_parser("scan", help="Scan a directory for authdrift patterns.")
|
|
67
|
+
scan.add_argument(
|
|
68
|
+
"path",
|
|
69
|
+
nargs="?",
|
|
70
|
+
default=".",
|
|
71
|
+
help="Path to scan (default: current directory).",
|
|
72
|
+
)
|
|
73
|
+
scan.add_argument(
|
|
74
|
+
"--json",
|
|
75
|
+
action="store_true",
|
|
76
|
+
help="Emit semgrep JSON output instead of human-readable.",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
args = parser.parse_args(argv)
|
|
80
|
+
|
|
81
|
+
if args.command == "scan":
|
|
82
|
+
return _scan(args.path, args.json)
|
|
83
|
+
|
|
84
|
+
parser.print_help()
|
|
85
|
+
return 1
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
if __name__ == "__main__":
|
|
89
|
+
sys.exit(main())
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
rules:
|
|
2
|
+
- id: oauth-passport-email-as-primary-key
|
|
3
|
+
message: |
|
|
4
|
+
This OAuth handler is using `profile.emails[0].value` as a user lookup key.
|
|
5
|
+
|
|
6
|
+
When a user renames their Gmail address (Google rolled out Gmail rename in
|
|
7
|
+
March 2026), the email returned by Google changes and your application will
|
|
8
|
+
silently create a duplicate user record — fragmenting their identity, audit
|
|
9
|
+
history, and ACLs. The same drift occurs in ~0.04% of normal Sign-in-with-Google
|
|
10
|
+
logins (Truffle Security, January 2025), independent of the rename feature.
|
|
11
|
+
|
|
12
|
+
Fix: use `profile.id` (the OIDC `sub` claim) as the immutable primary key.
|
|
13
|
+
Treat `profile.emails[0].value` as a mutable contact attribute, not an
|
|
14
|
+
identifier.
|
|
15
|
+
|
|
16
|
+
See: https://github.com/Neelagiri65/authdrift#why-this-matters
|
|
17
|
+
severity: WARNING
|
|
18
|
+
languages:
|
|
19
|
+
- javascript
|
|
20
|
+
- typescript
|
|
21
|
+
patterns:
|
|
22
|
+
- pattern-either:
|
|
23
|
+
- pattern: |
|
|
24
|
+
$FN({..., email: $P.emails[0].value, ...})
|
|
25
|
+
- pattern: |
|
|
26
|
+
$FN({..., where: {email: $P.emails[0].value}})
|
|
27
|
+
- pattern: |
|
|
28
|
+
$FN({..., where: {email: $P.emails[0].value, ...}})
|
|
29
|
+
|
|
30
|
+
- id: nextauth-signin-callback-email-as-primary-key
|
|
31
|
+
message: |
|
|
32
|
+
This NextAuth `signIn` flow is resolving users by `email`. When a Google user
|
|
33
|
+
renames their Gmail address (March 2026 rollout), the email returned by the
|
|
34
|
+
provider changes and a duplicate user record will be created on next sign-in.
|
|
35
|
+
|
|
36
|
+
Fix: key the lookup on `account.providerAccountId` (the OIDC `sub` claim) and
|
|
37
|
+
treat email as a mutable contact attribute.
|
|
38
|
+
|
|
39
|
+
See: https://github.com/Neelagiri65/authdrift#why-this-matters
|
|
40
|
+
severity: WARNING
|
|
41
|
+
languages:
|
|
42
|
+
- javascript
|
|
43
|
+
- typescript
|
|
44
|
+
patterns:
|
|
45
|
+
- pattern-either:
|
|
46
|
+
- pattern: |
|
|
47
|
+
prisma.user.findUnique({where: {email: $U.email}})
|
|
48
|
+
- pattern: |
|
|
49
|
+
prisma.user.findFirst({where: {email: $U.email}})
|
|
50
|
+
- pattern: |
|
|
51
|
+
db.user.findUnique({where: {email: $U.email}})
|
|
52
|
+
|
|
53
|
+
- id: python-google-oauth-userinfo-email-as-primary-key
|
|
54
|
+
message: |
|
|
55
|
+
This handler queries a user by the `email` field returned from Google's OAuth
|
|
56
|
+
userinfo response. When the user renames their Gmail address (March 2026
|
|
57
|
+
rollout), the email field changes and a duplicate account will be created.
|
|
58
|
+
|
|
59
|
+
Fix: store and look up by `userinfo['sub']` (the immutable subject ID).
|
|
60
|
+
Treat email as a mutable contact attribute.
|
|
61
|
+
|
|
62
|
+
See: https://github.com/Neelagiri65/authdrift#why-this-matters
|
|
63
|
+
severity: WARNING
|
|
64
|
+
languages:
|
|
65
|
+
- python
|
|
66
|
+
patterns:
|
|
67
|
+
- pattern-either:
|
|
68
|
+
- pattern: |
|
|
69
|
+
$MODEL.objects.get(email=$USERINFO['email'])
|
|
70
|
+
- pattern: |
|
|
71
|
+
$MODEL.objects.filter(email=$USERINFO['email']).first()
|
|
72
|
+
- pattern: |
|
|
73
|
+
$MODEL.query.filter_by(email=$USERINFO['email']).first()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Should trigger nextauth-signin-callback-email-as-primary-key
|
|
2
|
+
import NextAuth from 'next-auth';
|
|
3
|
+
import GoogleProvider from 'next-auth/providers/google';
|
|
4
|
+
import { prisma } from '@/lib/prisma';
|
|
5
|
+
|
|
6
|
+
export default NextAuth({
|
|
7
|
+
providers: [
|
|
8
|
+
GoogleProvider({
|
|
9
|
+
clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
10
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
11
|
+
}),
|
|
12
|
+
],
|
|
13
|
+
callbacks: {
|
|
14
|
+
async signIn({ user, account }) {
|
|
15
|
+
const existing = await prisma.user.findUnique({ where: { email: user.email } });
|
|
16
|
+
if (!existing) {
|
|
17
|
+
await prisma.user.create({
|
|
18
|
+
data: { email: user.email, name: user.name },
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Should NOT trigger — keys on profile.id (the OIDC sub claim)
|
|
2
|
+
const passport = require('passport');
|
|
3
|
+
const GoogleStrategy = require('passport-google-oauth20').Strategy;
|
|
4
|
+
|
|
5
|
+
passport.use(new GoogleStrategy({
|
|
6
|
+
clientID: process.env.GOOGLE_CLIENT_ID,
|
|
7
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
8
|
+
callbackURL: '/auth/google/callback',
|
|
9
|
+
}, async (accessToken, refreshToken, profile, done) => {
|
|
10
|
+
// Email is recorded as a contact attribute, but the lookup key is the immutable sub.
|
|
11
|
+
const user = await db.findUser({ googleId: profile.id });
|
|
12
|
+
if (!user) {
|
|
13
|
+
return db.createUser({
|
|
14
|
+
googleId: profile.id,
|
|
15
|
+
email: profile.emails[0].value,
|
|
16
|
+
name: profile.displayName,
|
|
17
|
+
}, done);
|
|
18
|
+
}
|
|
19
|
+
return done(null, user);
|
|
20
|
+
}));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Should trigger oauth-passport-email-as-primary-key
|
|
2
|
+
const passport = require('passport');
|
|
3
|
+
const GoogleStrategy = require('passport-google-oauth20').Strategy;
|
|
4
|
+
|
|
5
|
+
passport.use(new GoogleStrategy({
|
|
6
|
+
clientID: process.env.GOOGLE_CLIENT_ID,
|
|
7
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
8
|
+
callbackURL: '/auth/google/callback',
|
|
9
|
+
}, async (accessToken, refreshToken, profile, done) => {
|
|
10
|
+
const user = await db.findUser({ email: profile.emails[0].value });
|
|
11
|
+
if (!user) {
|
|
12
|
+
return db.createUser({ email: profile.emails[0].value, name: profile.displayName }, done);
|
|
13
|
+
}
|
|
14
|
+
return done(null, user);
|
|
15
|
+
}));
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Should trigger python-google-oauth-userinfo-email-as-primary-key
|
|
2
|
+
import requests
|
|
3
|
+
from django.contrib.auth.models import User
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def google_oauth_callback(request):
|
|
7
|
+
code = request.GET.get("code")
|
|
8
|
+
token_resp = requests.post(
|
|
9
|
+
"https://oauth2.googleapis.com/token",
|
|
10
|
+
data={
|
|
11
|
+
"code": code,
|
|
12
|
+
"client_id": "...",
|
|
13
|
+
"client_secret": "...",
|
|
14
|
+
"redirect_uri": "...",
|
|
15
|
+
"grant_type": "authorization_code",
|
|
16
|
+
},
|
|
17
|
+
)
|
|
18
|
+
access_token = token_resp.json()["access_token"]
|
|
19
|
+
userinfo = requests.get(
|
|
20
|
+
"https://www.googleapis.com/oauth2/v3/userinfo",
|
|
21
|
+
headers={"Authorization": f"Bearer {access_token}"},
|
|
22
|
+
).json()
|
|
23
|
+
|
|
24
|
+
user = User.objects.get(email=userinfo["email"])
|
|
25
|
+
return user
|