Fix Google OAuth: explicit IAM policy + strip OIDC scopes from credentials

This commit is contained in:
daniel
2026-05-08 16:57:40 -05:00
parent d68ddab8a2
commit 9b56aa83df
11 changed files with 288 additions and 36 deletions

View File

@@ -38,13 +38,20 @@ def _get_creds(actor_id: str) -> Credentials:
expiry = exp_aware.replace(tzinfo=None) # google-auth uses naive UTC datetimes expiry = exp_aware.replace(tzinfo=None) # google-auth uses naive UTC datetimes
else: else:
expiry = None expiry = None
stored_scopes = data.get('scopes', [])
api_scopes = [s for s in stored_scopes if s.startswith('https://')] if stored_scopes else None
# Fix stored scopes if they contain OIDC scopes
if stored_scopes and any(s in stored_scopes for s in ['openid', 'email', 'profile']):
data['scopes'] = api_scopes
_secrets().put_secret_value(SecretId=secret_name, SecretString=json.dumps(data))
print('[google] fixed stored scopes: removed OIDC scopes')
creds = Credentials( creds = Credentials(
token=data.get('token'), token=data.get('token'),
refresh_token=data.get('refresh_token'), refresh_token=data.get('refresh_token'),
token_uri=data.get('token_uri', 'https://oauth2.googleapis.com/token'), token_uri=data.get('token_uri', 'https://oauth2.googleapis.com/token'),
client_id=data.get('client_id'), client_id=data.get('client_id'),
client_secret=data.get('client_secret'), client_secret=data.get('client_secret'),
scopes=data.get('scopes'), scopes=api_scopes,
expiry=expiry, expiry=expiry,
) )
print(f'[google] creds loaded, expired={creds.expired}') print(f'[google] creds loaded, expired={creds.expired}')

View File

@@ -31,16 +31,16 @@
} }
} }
}, },
"a6d7ca10ce41a486503b8ea9f109a54841bb31af9548c618fdca79ac13b34c6a": { "b45b92872bd4af9d3688817f862e6574ff6b4903e68b140bcee6fe0b2678c645": {
"displayName": "OAuthHandler/Code", "displayName": "OAuthHandler/Code",
"source": { "source": {
"path": "asset.a6d7ca10ce41a486503b8ea9f109a54841bb31af9548c618fdca79ac13b34c6a", "path": "asset.b45b92872bd4af9d3688817f862e6574ff6b4903e68b140bcee6fe0b2678c645",
"packaging": "zip" "packaging": "zip"
}, },
"destinations": { "destinations": {
"495395224548-us-east-1-77bd44d3": { "495395224548-us-east-1-d4c72dd0": {
"bucketName": "cdk-hnb659fds-assets-495395224548-us-east-1", "bucketName": "cdk-hnb659fds-assets-495395224548-us-east-1",
"objectKey": "a6d7ca10ce41a486503b8ea9f109a54841bb31af9548c618fdca79ac13b34c6a.zip", "objectKey": "b45b92872bd4af9d3688817f862e6574ff6b4903e68b140bcee6fe0b2678c645.zip",
"region": "us-east-1", "region": "us-east-1",
"assumeRoleArn": "arn:${AWS::Partition}:iam::495395224548:role/cdk-hnb659fds-file-publishing-role-495395224548-us-east-1" "assumeRoleArn": "arn:${AWS::Partition}:iam::495395224548:role/cdk-hnb659fds-file-publishing-role-495395224548-us-east-1"
} }
@@ -61,16 +61,16 @@
} }
} }
}, },
"6c96ac78834e047807c02e9e41e5a6f43de9b760bc3954d97cb2c3df560d71e7": { "7cdf99af915f7191eec65aef2668994abc0bff90a30effd9c6f67d7723bcfad0": {
"displayName": "AgentClawStack Template", "displayName": "AgentClawStack Template",
"source": { "source": {
"path": "AgentClawStack.template.json", "path": "AgentClawStack.template.json",
"packaging": "file" "packaging": "file"
}, },
"destinations": { "destinations": {
"495395224548-us-east-1-014f016d": { "495395224548-us-east-1-41667eab": {
"bucketName": "cdk-hnb659fds-assets-495395224548-us-east-1", "bucketName": "cdk-hnb659fds-assets-495395224548-us-east-1",
"objectKey": "6c96ac78834e047807c02e9e41e5a6f43de9b760bc3954d97cb2c3df560d71e7.json", "objectKey": "7cdf99af915f7191eec65aef2668994abc0bff90a30effd9c6f67d7723bcfad0.json",
"region": "us-east-1", "region": "us-east-1",
"assumeRoleArn": "arn:${AWS::Partition}:iam::495395224548:role/cdk-hnb659fds-file-publishing-role-495395224548-us-east-1" "assumeRoleArn": "arn:${AWS::Partition}:iam::495395224548:role/cdk-hnb659fds-file-publishing-role-495395224548-us-east-1"
} }

View File

@@ -32,7 +32,7 @@
{ {
"type": "aws:cdk:creationStack", "type": "aws:cdk:creationStack",
"data": [ "data": [
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:342:5)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:348:5)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]
@@ -46,7 +46,7 @@
{ {
"type": "aws:cdk:creationStack", "type": "aws:cdk:creationStack",
"data": [ "data": [
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:346:5)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:352:5)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]
@@ -60,7 +60,7 @@
{ {
"type": "aws:cdk:creationStack", "type": "aws:cdk:creationStack",
"data": [ "data": [
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:350:5)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:356:5)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]
@@ -74,7 +74,7 @@
{ {
"type": "aws:cdk:creationStack", "type": "aws:cdk:creationStack",
"data": [ "data": [
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:355:5)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:361:5)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]
@@ -88,7 +88,7 @@
{ {
"type": "aws:cdk:creationStack", "type": "aws:cdk:creationStack",
"data": [ "data": [
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:360:5)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:366:5)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]
@@ -102,7 +102,7 @@
{ {
"type": "aws:cdk:creationStack", "type": "aws:cdk:creationStack",
"data": [ "data": [
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:365:5)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:371:5)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]
@@ -116,7 +116,7 @@
{ {
"type": "aws:cdk:creationStack", "type": "aws:cdk:creationStack",
"data": [ "data": [
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:370:5)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:376:5)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]
@@ -130,7 +130,7 @@
{ {
"type": "aws:cdk:creationStack", "type": "aws:cdk:creationStack",
"data": [ "data": [
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:375:5)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:381:5)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]
@@ -144,7 +144,7 @@
{ {
"type": "aws:cdk:creationStack", "type": "aws:cdk:creationStack",
"data": [ "data": [
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:380:5)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:386:5)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]
@@ -158,7 +158,7 @@
{ {
"type": "aws:cdk:creationStack", "type": "aws:cdk:creationStack",
"data": [ "data": [
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:385:5)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:391:5)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]
@@ -319,7 +319,7 @@
"type": "aws:cdk:creationStack", "type": "aws:cdk:creationStack",
"data": [ "data": [
"...new Function2 in aws-cdk-lib...", "...new Function2 in aws-cdk-lib...",
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:301:25)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:307:25)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]
@@ -334,7 +334,7 @@
"type": "aws:cdk:creationStack", "type": "aws:cdk:creationStack",
"data": [ "data": [
"...WrappedClass.addPermission in aws-cdk-lib...", "...WrappedClass.addPermission in aws-cdk-lib...",
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:314:17)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:320:17)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]
@@ -458,7 +458,7 @@
".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...", ".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...",
"Array.map (:)", "Array.map (:)",
"...WrappedClass.<anonymous> in aws-cdk-lib...", "...WrappedClass.<anonymous> in aws-cdk-lib...",
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:266:13)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:272:13)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]
@@ -475,7 +475,7 @@
".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...", ".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...",
"Array.map (:)", "Array.map (:)",
"...WrappedClass.<anonymous> in aws-cdk-lib...", "...WrappedClass.<anonymous> in aws-cdk-lib...",
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:266:13)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:272:13)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]
@@ -492,7 +492,7 @@
".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...", ".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...",
"Array.map (:)", "Array.map (:)",
"...WrappedClass.<anonymous> in aws-cdk-lib...", "...WrappedClass.<anonymous> in aws-cdk-lib...",
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:273:13)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:279:13)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]
@@ -509,7 +509,7 @@
".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...", ".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...",
"Array.map (:)", "Array.map (:)",
"...WrappedClass.<anonymous> in aws-cdk-lib...", "...WrappedClass.<anonymous> in aws-cdk-lib...",
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:273:13)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:279:13)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]
@@ -526,7 +526,7 @@
".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...", ".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...",
"Array.map (:)", "Array.map (:)",
"...WrappedClass.<anonymous> in aws-cdk-lib...", "...WrappedClass.<anonymous> in aws-cdk-lib...",
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:282:13)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:288:13)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]
@@ -543,7 +543,7 @@
".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...", ".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...",
"Array.map (:)", "Array.map (:)",
"...WrappedClass.<anonymous> in aws-cdk-lib...", "...WrappedClass.<anonymous> in aws-cdk-lib...",
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:282:13)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:288:13)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]
@@ -603,7 +603,7 @@
"type": "aws:cdk:creationStack", "type": "aws:cdk:creationStack",
"data": [ "data": [
"...new Function2 in aws-cdk-lib...", "...new Function2 in aws-cdk-lib...",
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:301:25)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:307:25)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]
@@ -667,7 +667,7 @@
".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...", ".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...",
"Array.map (:)", "Array.map (:)",
"...WrappedClass.<anonymous> in aws-cdk-lib...", "...WrappedClass.<anonymous> in aws-cdk-lib...",
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:266:13)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:272:13)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]
@@ -684,7 +684,7 @@
".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...", ".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...",
"Array.map (:)", "Array.map (:)",
"...WrappedClass.<anonymous> in aws-cdk-lib...", "...WrappedClass.<anonymous> in aws-cdk-lib...",
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:273:13)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:279:13)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]
@@ -701,7 +701,7 @@
".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...", ".../Users/daniel/agent-claw/cdk/node_modules/aws-cdk-lib/aws-apigatewayv2/lib/http/api.js:1:96 in aws-cdk-lib...",
"Array.map (:)", "Array.map (:)",
"...WrappedClass.<anonymous> in aws-cdk-lib...", "...WrappedClass.<anonymous> in aws-cdk-lib...",
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:282:13)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:288:13)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]
@@ -731,7 +731,7 @@
"type": "aws:cdk:creationStack", "type": "aws:cdk:creationStack",
"data": [ "data": [
"...environmentFromArn.grantRead in aws-cdk-lib...", "...environmentFromArn.grantRead in aws-cdk-lib...",
"new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:312:20)", "new AgentClawStack (/Users/daniel/agent-claw/cdk/lib/agent-claw-stack.ts:318:20)",
"<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)", "<anonymous> (/Users/daniel/agent-claw/cdk/bin/agent-claw.ts:8:1)",
"...node internals, ts-node, ts-node, ts-node..." "...node internals, ts-node, ts-node, ts-node..."
] ]

View File

@@ -1147,6 +1147,12 @@
} }
] ]
}, },
{
"Action": "secretsmanager:GetSecretValue",
"Effect": "Allow",
"Resource": "arn:aws:secretsmanager:us-east-1:495395224548:secret:agent-claw/google-oauth-client-subXHl",
"Sid": "GoogleOAuthClientSecretExact"
},
{ {
"Action": [ "Action": [
"secretsmanager:CreateSecret", "secretsmanager:CreateSecret",
@@ -1176,7 +1182,7 @@
"Properties": { "Properties": {
"Code": { "Code": {
"S3Bucket": "cdk-hnb659fds-assets-495395224548-us-east-1", "S3Bucket": "cdk-hnb659fds-assets-495395224548-us-east-1",
"S3Key": "a6d7ca10ce41a486503b8ea9f109a54841bb31af9548c618fdca79ac13b34c6a.zip" "S3Key": "b45b92872bd4af9d3688817f862e6574ff6b4903e68b140bcee6fe0b2678c645.zip"
}, },
"Environment": { "Environment": {
"Variables": { "Variables": {
@@ -1232,7 +1238,7 @@
], ],
"Metadata": { "Metadata": {
"aws:cdk:path": "AgentClawStack/OAuthHandler/Resource", "aws:cdk:path": "AgentClawStack/OAuthHandler/Resource",
"aws:asset:path": "asset.a6d7ca10ce41a486503b8ea9f109a54841bb31af9548c618fdca79ac13b34c6a", "aws:asset:path": "asset.b45b92872bd4af9d3688817f862e6574ff6b4903e68b140bcee6fe0b2678c645",
"aws:asset:is-bundled": false, "aws:asset:is-bundled": false,
"aws:asset:property": "Code" "aws:asset:property": "Code"
} }

View File

@@ -0,0 +1,232 @@
"""
Google OAuth handler Lambda.
Routes:
GET /oauth/start?actor_id=telegram:123 → redirect to Google OAuth consent
GET /oauth/callback?code=...&state=... → exchange code, store tokens, update DynamoDB
"""
import base64
import hashlib
import hmac
import json
import os
import time
import urllib.parse
import urllib.request
import boto3
_sm = None
_ddb = None
SCOPES = ' '.join([
'https://www.googleapis.com/auth/gmail.modify',
'https://www.googleapis.com/auth/calendar',
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/documents',
'openid',
'email',
'profile',
])
def get_sm():
global _sm
if _sm is None:
_sm = boto3.client('secretsmanager', region_name=os.environ.get('AWS_REGION', 'us-east-1'))
return _sm
def get_ddb():
global _ddb
if _ddb is None:
_ddb = boto3.resource('dynamodb')
return _ddb
def get_oauth_client() -> tuple[str, str]:
"""Return (client_id, client_secret) from Secrets Manager."""
arn = os.environ['GOOGLE_OAUTH_CLIENT_SECRET_ARN']
secret = json.loads(get_sm().get_secret_value(SecretId=arn)['SecretString'])
return secret['client_id'], secret['client_secret']
def actor_id_to_secret_name(actor_id: str) -> str:
safe = actor_id.replace(':', '-').replace('/', '-')
return f'agent-claw/google-credentials/{safe}'
def _redirect(url: str) -> dict:
return {'statusCode': 302, 'headers': {'Location': url}, 'body': ''}
def _html(body: str, status: int = 200) -> dict:
return {'statusCode': status, 'headers': {'Content-Type': 'text/html'}, 'body': body}
def handler(event, context):
path = event.get('rawPath') or event.get('path', '')
params = event.get('queryStringParameters') or {}
if path.endswith('/oauth/start'):
return handle_start(params)
elif path.endswith('/oauth/callback'):
return handle_callback(params)
else:
return {'statusCode': 404, 'body': 'Not found'}
def handle_start(params: dict) -> dict:
actor_id = params.get('actor_id', '')
if not actor_id:
return _html('<h1>Missing actor_id</h1>', 400)
client_id, _ = get_oauth_client()
redirect_uri = os.environ['OAUTH_REDIRECT_URI']
# Encode actor_id in state (base64 to keep URL-safe)
state = base64.urlsafe_b64encode(actor_id.encode()).decode().rstrip('=')
auth_url = (
'https://accounts.google.com/o/oauth2/v2/auth?'
+ urllib.parse.urlencode({
'client_id': client_id,
'redirect_uri': redirect_uri,
'response_type': 'code',
'scope': SCOPES,
'access_type': 'offline',
'prompt': 'consent',
'state': state,
})
)
return _redirect(auth_url)
def handle_callback(params: dict) -> dict:
code = params.get('code', '')
state = params.get('state', '')
error = params.get('error', '')
if error:
return _html(f'<h1>OAuth error: {error}</h1>', 400)
if not code or not state:
return _html('<h1>Missing code or state</h1>', 400)
# Decode actor_id from state
try:
padding = 4 - len(state) % 4
actor_id = base64.urlsafe_b64decode(state + '=' * padding).decode()
except Exception:
return _html('<h1>Invalid state</h1>', 400)
client_id, client_secret = get_oauth_client()
redirect_uri = os.environ['OAUTH_REDIRECT_URI']
# Exchange code for tokens
token_data = urllib.parse.urlencode({
'code': code,
'client_id': client_id,
'client_secret': client_secret,
'redirect_uri': redirect_uri,
'grant_type': 'authorization_code',
}).encode()
req = urllib.request.Request(
'https://oauth2.googleapis.com/token',
data=token_data,
headers={'Content-Type': 'application/x-www-form-urlencoded'},
)
try:
with urllib.request.urlopen(req, timeout=15) as resp:
tokens = json.loads(resp.read())
except Exception as e:
print(f'[oauth] Token exchange failed: {e}')
return _html(f'<h1>Token exchange failed: {e}</h1>', 500)
# Fetch user email from Google
user_email = ''
try:
id_token_payload = tokens.get('id_token', '').split('.')[1]
padding = 4 - len(id_token_payload) % 4
claims = json.loads(base64.urlsafe_b64decode(id_token_payload + '=' * padding))
user_email = claims.get('email', '')
except Exception:
pass
if not user_email:
# Fallback: call userinfo endpoint
try:
access_token = tokens.get('access_token', '')
req2 = urllib.request.Request(
'https://www.googleapis.com/oauth2/v3/userinfo',
headers={'Authorization': f'Bearer {access_token}'},
)
with urllib.request.urlopen(req2, timeout=10) as resp2:
user_email = json.loads(resp2.read()).get('email', '')
except Exception as e:
print(f'[oauth] userinfo fetch failed: {e}')
# Build credentials dict (google-auth format)
creds = {
'token': tokens.get('access_token'),
'refresh_token': tokens.get('refresh_token'),
'token_uri': 'https://oauth2.googleapis.com/token',
'client_id': client_id,
'client_secret': client_secret,
'scopes': [s for s in SCOPES.split() if s.startswith('https://')],
'email': user_email,
'user_email': user_email,
}
if tokens.get('expires_in'):
creds['expiry'] = time.strftime(
'%Y-%m-%dT%H:%M:%SZ',
time.gmtime(time.time() + int(tokens['expires_in']))
)
# Store in Secrets Manager
secret_name = actor_id_to_secret_name(actor_id)
sm = get_sm()
try:
sm.create_secret(Name=secret_name, SecretString=json.dumps(creds))
except sm.exceptions.ResourceExistsException:
sm.put_secret_value(SecretId=secret_name, SecretString=json.dumps(creds))
print(f'[oauth] Stored credentials for actor={actor_id} email={user_email}')
# Update DynamoDB users table with google_email
table_name = os.environ.get('USERS_TABLE_NAME', '')
if table_name and actor_id:
try:
get_ddb().Table(table_name).update_item(
Key={'actor_id': actor_id},
UpdateExpression='SET google_email = :e',
ExpressionAttributeValues={':e': user_email},
)
except Exception as e:
print(f'[oauth] DynamoDB update failed: {e}')
# Best-effort Telegram confirmation
try:
bot_token_arn = os.environ.get('TELEGRAM_BOT_TOKEN_SECRET_ARN', '')
if bot_token_arn and actor_id.startswith('telegram:'):
chat_id = actor_id.split(':', 1)[1]
bot_token = get_sm().get_secret_value(SecretId=bot_token_arn)['SecretString']
tg_text = (
f'✅ Google account connected!\n\n'
f'{user_email} is now linked. You can now ask me about your Gmail, Calendar, and Drive.'
)
tg_payload = json.dumps({'chat_id': chat_id, 'text': tg_text}).encode()
tg_req = urllib.request.Request(
f'https://api.telegram.org/bot{bot_token}/sendMessage',
data=tg_payload,
headers={'Content-Type': 'application/json'},
)
urllib.request.urlopen(tg_req, timeout=5)
except Exception as e:
print(f'[oauth] Telegram notification failed: {e}')
return _html(
f'<h1>✅ Google account connected!</h1>'
f'<p>Connected <b>{user_email}</b> to your agent account.</p>'
f'<p>You can close this window and return to Telegram.</p>'
)

View File

@@ -18,7 +18,7 @@
"validateOnSynth": false, "validateOnSynth": false,
"assumeRoleArn": "arn:${AWS::Partition}:iam::495395224548:role/cdk-hnb659fds-deploy-role-495395224548-us-east-1", "assumeRoleArn": "arn:${AWS::Partition}:iam::495395224548:role/cdk-hnb659fds-deploy-role-495395224548-us-east-1",
"cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::495395224548:role/cdk-hnb659fds-cfn-exec-role-495395224548-us-east-1", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::495395224548:role/cdk-hnb659fds-cfn-exec-role-495395224548-us-east-1",
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-495395224548-us-east-1/6c96ac78834e047807c02e9e41e5a6f43de9b760bc3954d97cb2c3df560d71e7.json", "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-495395224548-us-east-1/7cdf99af915f7191eec65aef2668994abc0bff90a30effd9c6f67d7723bcfad0.json",
"requiresBootstrapStackVersion": 6, "requiresBootstrapStackVersion": 6,
"bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
"additionalDependencies": [ "additionalDependencies": [

File diff suppressed because one or more lines are too long

View File

@@ -251,6 +251,12 @@ export class AgentClawStack extends cdk.Stack {
googleOAuthClientSecret.grantRead(oauthHandlerFn); googleOAuthClientSecret.grantRead(oauthHandlerFn);
botTokenSecret.grantRead(oauthHandlerFn); botTokenSecret.grantRead(oauthHandlerFn);
usersTable.grantReadWriteData(oauthHandlerFn); usersTable.grantReadWriteData(oauthHandlerFn);
// Explicit access to the OAuth client secret (fromSecretNameV2 wildcard may not resolve)
oauthHandlerFn.addToRolePolicy(new iam.PolicyStatement({
sid: 'GoogleOAuthClientSecretExact',
actions: ['secretsmanager:GetSecretValue'],
resources: ['arn:aws:secretsmanager:us-east-1:495395224548:secret:agent-claw/google-oauth-client-subXHl'],
}));
// Grant OAuth handler write access to per-user credential secrets // Grant OAuth handler write access to per-user credential secrets
oauthHandlerFn.addToRolePolicy(new iam.PolicyStatement({ oauthHandlerFn.addToRolePolicy(new iam.PolicyStatement({
sid: 'PerUserGoogleCredentialsWrite', sid: 'PerUserGoogleCredentialsWrite',

View File

@@ -174,7 +174,7 @@ def handle_callback(params: dict) -> dict:
'token_uri': 'https://oauth2.googleapis.com/token', 'token_uri': 'https://oauth2.googleapis.com/token',
'client_id': client_id, 'client_id': client_id,
'client_secret': client_secret, 'client_secret': client_secret,
'scopes': SCOPES.split(), 'scopes': [s for s in SCOPES.split() if s.startswith('https://')],
'email': user_email, 'email': user_email,
'user_email': user_email, 'user_email': user_email,
} }