omniauth-yahoojp 1.0.0 → 1.0.2
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.
- checksums.yaml +4 -4
- data/CLAUDE.md +2 -2
- data/README.md +9 -2
- data/lib/omniauth/strategies/yahoojp.rb +30 -4
- data/lib/omniauth-yahoojp/version.rb +1 -1
- data/spec/omniauth/strategies/yahoojp_spec.rb +197 -5
- metadata +2 -4
- data/.github/workflows/claude-code-review.yml +0 -78
- data/.github/workflows/claude.yml +0 -64
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3e2e5091640143dd38a711507353562df857f8f78a58c533515b8f16cc498ea5
|
|
4
|
+
data.tar.gz: 8017e5b101087d514d661455e6a51e40d17397a3642aa2863183863ee37c4ba4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3768a8277939e04cb7bfaa0802b4e419f25df351d99a15d7f50ea2d710134b00179914eeb030c42af12370a6d815b8281cd8f0474dc32f325a0400b572f43e76
|
|
7
|
+
data.tar.gz: e7565557e3f45447be3c612a4aeb66cad363112a395a96ecbfbf73fb420e8ce2a7af9116624961a383ca2522f07e60a98f87781d1abfd4e741c1b7e509980d8f
|
data/CLAUDE.md
CHANGED
|
@@ -44,6 +44,6 @@ This is a Ruby gem that implements an OmniAuth strategy for Yahoo! JAPAN's YConn
|
|
|
44
44
|
|
|
45
45
|
## Downstream Test Project
|
|
46
46
|
|
|
47
|
-
- [`omniauth-yahoojp-tester-
|
|
47
|
+
- [`omniauth-yahoojp-tester-containers`](https://github.com/mikanmarusan/omniauth-yahoojp-tester-containers) is an app that consumes this gem for integration testing.
|
|
48
48
|
- Its CLAUDE.md documents the exact API surface used and cross-project update rules.
|
|
49
|
-
- Use `/add-dir ../omniauth-yahoojp-tester-
|
|
49
|
+
- Use `/add-dir ../omniauth-yahoojp-tester-containers` to add it for cross-project awareness when making API changes.
|
data/README.md
CHANGED
|
@@ -65,9 +65,16 @@ Controls how user profile information is retrieved.
|
|
|
65
65
|
When the `openid` scope is requested, the strategy automatically captures the `id_token` returned by Yahoo! JAPAN's token endpoint.
|
|
66
66
|
|
|
67
67
|
- `credentials.id_token` — The raw JWT string as returned from the token endpoint.
|
|
68
|
-
- `extra.id_token_claims` — The decoded claims hash from the `id_token`.
|
|
68
|
+
- `extra.id_token_claims` — The decoded and verified claims hash from the `id_token`.
|
|
69
69
|
|
|
70
|
-
The `id_token`
|
|
70
|
+
The `id_token` is verified as follows:
|
|
71
|
+
|
|
72
|
+
1. **RS256 signature verification** — The token signature is verified using the public key fetched from Yahoo! JAPAN's [JWKS endpoint](https://auth.login.yahoo.co.jp/yconnect/v2/jwks), matched by the `kid` header claim.
|
|
73
|
+
2. **Issuer (`iss`) validation** — Must be `https://auth.login.yahoo.co.jp/yconnect/v2`.
|
|
74
|
+
3. **Audience (`aud`) validation** — Must include your application's client ID.
|
|
75
|
+
4. **Expiration (`exp`) validation** — The token must not be expired (with a 30-second leeway for clock skew).
|
|
76
|
+
|
|
77
|
+
If any verification step fails, an `OmniAuth::Strategies::YahooJp::IdTokenValidationError` is raised, which is handled by OmniAuth's standard error flow.
|
|
71
78
|
|
|
72
79
|
### API Version
|
|
73
80
|
|
|
@@ -5,6 +5,10 @@ require 'json/jwt'
|
|
|
5
5
|
module OmniAuth
|
|
6
6
|
module Strategies
|
|
7
7
|
class YahooJp < OmniAuth::Strategies::OAuth2
|
|
8
|
+
class IdTokenValidationError < StandardError; end
|
|
9
|
+
|
|
10
|
+
JWKS_URI = 'https://auth.login.yahoo.co.jp/yconnect/v2/jwks'.freeze
|
|
11
|
+
ISSUER = 'https://auth.login.yahoo.co.jp/yconnect/v2'.freeze
|
|
8
12
|
|
|
9
13
|
option :name, 'yahoojp'
|
|
10
14
|
option :client_options, {
|
|
@@ -63,7 +67,7 @@ module OmniAuth
|
|
|
63
67
|
access_token.options[:mode] = :header
|
|
64
68
|
access_token.get('https://userinfo.yahooapis.jp/yconnect/v2/attribute').parsed
|
|
65
69
|
elsif id_token
|
|
66
|
-
id_token_claims
|
|
70
|
+
id_token_claims || {}
|
|
67
71
|
else
|
|
68
72
|
{}
|
|
69
73
|
end
|
|
@@ -75,9 +79,9 @@ module OmniAuth
|
|
|
75
79
|
|
|
76
80
|
def id_token_claims
|
|
77
81
|
return nil unless id_token
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
@id_token_claims ||= verify_id_token!
|
|
83
|
+
rescue JSON::JWT::InvalidFormat
|
|
84
|
+
nil
|
|
81
85
|
end
|
|
82
86
|
|
|
83
87
|
def prune!(hash)
|
|
@@ -102,6 +106,28 @@ module OmniAuth
|
|
|
102
106
|
full_host + script_name + callback_path
|
|
103
107
|
end
|
|
104
108
|
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def verify_id_token!
|
|
112
|
+
header = JSON::JWT.decode(id_token, :skip_verification).header
|
|
113
|
+
jwk = JSON::JWK::Set::Fetcher.fetch(JWKS_URI, kid: header['kid'])
|
|
114
|
+
claims = JSON::JWT.decode(id_token, jwk, [:RS256])
|
|
115
|
+
validate_id_token_claims!(claims)
|
|
116
|
+
claims
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def validate_id_token_claims!(claims)
|
|
120
|
+
unless claims['iss'] == ISSUER
|
|
121
|
+
raise IdTokenValidationError, "Invalid issuer: #{claims['iss']}"
|
|
122
|
+
end
|
|
123
|
+
unless Array(claims['aud']).include?(client.id)
|
|
124
|
+
raise IdTokenValidationError, "Invalid audience: #{claims['aud']}"
|
|
125
|
+
end
|
|
126
|
+
if claims['exp'].nil? || Time.now.to_i > claims['exp'].to_i + 30
|
|
127
|
+
raise IdTokenValidationError, 'id_token has expired'
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
105
131
|
end
|
|
106
132
|
end
|
|
107
133
|
end
|
|
@@ -26,9 +26,34 @@ RSpec.describe OmniAuth::Strategies::YahooJp do
|
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
describe 'constants' do
|
|
30
|
+
it 'has correct JWKS_URI' do
|
|
31
|
+
expect(described_class::JWKS_URI).to eq('https://auth.login.yahoo.co.jp/yconnect/v2/jwks')
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'has correct ISSUER' do
|
|
35
|
+
expect(described_class::ISSUER).to eq('https://auth.login.yahoo.co.jp/yconnect/v2')
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
29
39
|
context 'with access_token' do
|
|
30
|
-
let(:
|
|
31
|
-
let(:
|
|
40
|
+
let(:rsa_key) { OpenSSL::PKey::RSA.generate(2048) }
|
|
41
|
+
let(:kid) { 'test-kid-1' }
|
|
42
|
+
let(:jwt_payload) do
|
|
43
|
+
{
|
|
44
|
+
'sub' => 'test123',
|
|
45
|
+
'name' => 'Test User',
|
|
46
|
+
'email' => 'test@example.com',
|
|
47
|
+
'iss' => 'https://auth.login.yahoo.co.jp/yconnect/v2',
|
|
48
|
+
'aud' => 'client_id',
|
|
49
|
+
'exp' => Time.now.to_i + 3600
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
let(:jwt_string) do
|
|
53
|
+
jwt = JSON::JWT.new(jwt_payload)
|
|
54
|
+
jwt.kid = kid
|
|
55
|
+
jwt.sign(rsa_key, :RS256).to_s
|
|
56
|
+
end
|
|
32
57
|
let(:access_token) do
|
|
33
58
|
instance_double(
|
|
34
59
|
OAuth2::AccessToken,
|
|
@@ -43,6 +68,13 @@ RSpec.describe OmniAuth::Strategies::YahooJp do
|
|
|
43
68
|
|
|
44
69
|
before do
|
|
45
70
|
allow(strategy).to receive(:access_token).and_return(access_token)
|
|
71
|
+
jwk = JSON::JWK.new(rsa_key, kid: kid)
|
|
72
|
+
stub_request(:get, 'https://auth.login.yahoo.co.jp/yconnect/v2/jwks')
|
|
73
|
+
.to_return(
|
|
74
|
+
status: 200,
|
|
75
|
+
body: { keys: [jwk] }.to_json,
|
|
76
|
+
headers: { 'Content-Type' => 'application/json' }
|
|
77
|
+
)
|
|
46
78
|
end
|
|
47
79
|
|
|
48
80
|
describe '#id_token' do
|
|
@@ -62,7 +94,7 @@ RSpec.describe OmniAuth::Strategies::YahooJp do
|
|
|
62
94
|
end
|
|
63
95
|
|
|
64
96
|
describe '#id_token_claims' do
|
|
65
|
-
it 'decodes the JWT
|
|
97
|
+
it 'decodes and verifies the JWT' do
|
|
66
98
|
claims = strategy.id_token_claims
|
|
67
99
|
expect(claims['sub']).to eq('test123')
|
|
68
100
|
expect(claims['name']).to eq('Test User')
|
|
@@ -80,9 +112,158 @@ RSpec.describe OmniAuth::Strategies::YahooJp do
|
|
|
80
112
|
expect(strategy.id_token_claims).to be_nil
|
|
81
113
|
end
|
|
82
114
|
|
|
83
|
-
it '
|
|
115
|
+
it 'returns nil without fetching JWKS when id_token is nil' do
|
|
116
|
+
allow(access_token).to receive(:params).and_return({})
|
|
117
|
+
strategy.id_token_claims
|
|
118
|
+
expect(WebMock).not_to have_requested(:get, 'https://auth.login.yahoo.co.jp/yconnect/v2/jwks')
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it 'returns nil on malformed id_token' do
|
|
84
122
|
allow(access_token).to receive(:params).and_return({ 'id_token' => 'not-a-jwt' })
|
|
85
|
-
expect
|
|
123
|
+
expect(strategy.id_token_claims).to be_nil
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
context 'with invalid signature' do
|
|
127
|
+
let(:wrong_key) { OpenSSL::PKey::RSA.generate(2048) }
|
|
128
|
+
let(:bad_jwt_string) do
|
|
129
|
+
jwt = JSON::JWT.new(jwt_payload)
|
|
130
|
+
jwt.kid = kid
|
|
131
|
+
jwt.sign(wrong_key, :RS256).to_s
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
before do
|
|
135
|
+
allow(access_token).to receive(:params).and_return({ 'id_token' => bad_jwt_string })
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
it 'raises an error' do
|
|
139
|
+
expect { strategy.id_token_claims }.to raise_error(JSON::JWS::VerificationFailed)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
context 'with invalid issuer' do
|
|
144
|
+
let(:jwt_payload_bad_iss) { jwt_payload.merge('iss' => 'https://evil.example.com') }
|
|
145
|
+
let(:bad_iss_jwt) do
|
|
146
|
+
jwt = JSON::JWT.new(jwt_payload_bad_iss)
|
|
147
|
+
jwt.kid = kid
|
|
148
|
+
jwt.sign(rsa_key, :RS256).to_s
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
before do
|
|
152
|
+
allow(access_token).to receive(:params).and_return({ 'id_token' => bad_iss_jwt })
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it 'raises IdTokenValidationError' do
|
|
156
|
+
expect { strategy.id_token_claims }.to raise_error(
|
|
157
|
+
described_class::IdTokenValidationError, /Invalid issuer/
|
|
158
|
+
)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
context 'with issuer missing /yconnect/v2 suffix' do
|
|
163
|
+
let(:jwt_payload_wrong_iss) { jwt_payload.merge('iss' => 'https://auth.login.yahoo.co.jp') }
|
|
164
|
+
let(:wrong_iss_jwt) do
|
|
165
|
+
jwt = JSON::JWT.new(jwt_payload_wrong_iss)
|
|
166
|
+
jwt.kid = kid
|
|
167
|
+
jwt.sign(rsa_key, :RS256).to_s
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
before do
|
|
171
|
+
allow(access_token).to receive(:params).and_return({ 'id_token' => wrong_iss_jwt })
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it 'raises IdTokenValidationError' do
|
|
175
|
+
expect { strategy.id_token_claims }.to raise_error(
|
|
176
|
+
described_class::IdTokenValidationError, /Invalid issuer/
|
|
177
|
+
)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
context 'with invalid audience' do
|
|
182
|
+
let(:jwt_payload_bad_aud) { jwt_payload.merge('aud' => 'wrong_client_id') }
|
|
183
|
+
let(:bad_aud_jwt) do
|
|
184
|
+
jwt = JSON::JWT.new(jwt_payload_bad_aud)
|
|
185
|
+
jwt.kid = kid
|
|
186
|
+
jwt.sign(rsa_key, :RS256).to_s
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
before do
|
|
190
|
+
allow(access_token).to receive(:params).and_return({ 'id_token' => bad_aud_jwt })
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
it 'raises IdTokenValidationError' do
|
|
194
|
+
expect { strategy.id_token_claims }.to raise_error(
|
|
195
|
+
described_class::IdTokenValidationError, /Invalid audience/
|
|
196
|
+
)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
context 'with audience as array including client_id' do
|
|
201
|
+
let(:jwt_payload_array_aud) { jwt_payload.merge('aud' => ['other_client', 'client_id']) }
|
|
202
|
+
let(:array_aud_jwt) do
|
|
203
|
+
jwt = JSON::JWT.new(jwt_payload_array_aud)
|
|
204
|
+
jwt.kid = kid
|
|
205
|
+
jwt.sign(rsa_key, :RS256).to_s
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
before do
|
|
209
|
+
allow(access_token).to receive(:params).and_return({ 'id_token' => array_aud_jwt })
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
it 'accepts the token' do
|
|
213
|
+
expect { strategy.id_token_claims }.not_to raise_error
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
context 'with expired token' do
|
|
218
|
+
let(:jwt_payload_expired) { jwt_payload.merge('exp' => Time.now.to_i - 60) }
|
|
219
|
+
let(:expired_jwt) do
|
|
220
|
+
jwt = JSON::JWT.new(jwt_payload_expired)
|
|
221
|
+
jwt.kid = kid
|
|
222
|
+
jwt.sign(rsa_key, :RS256).to_s
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
before do
|
|
226
|
+
allow(access_token).to receive(:params).and_return({ 'id_token' => expired_jwt })
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
it 'raises IdTokenValidationError' do
|
|
230
|
+
expect { strategy.id_token_claims }.to raise_error(
|
|
231
|
+
described_class::IdTokenValidationError, /expired/
|
|
232
|
+
)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
context 'with token within 30-second leeway' do
|
|
237
|
+
let(:jwt_payload_just_expired) { jwt_payload.merge('exp' => Time.now.to_i - 10) }
|
|
238
|
+
let(:leeway_jwt) do
|
|
239
|
+
jwt = JSON::JWT.new(jwt_payload_just_expired)
|
|
240
|
+
jwt.kid = kid
|
|
241
|
+
jwt.sign(rsa_key, :RS256).to_s
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
before do
|
|
245
|
+
allow(access_token).to receive(:params).and_return({ 'id_token' => leeway_jwt })
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
it 'accepts the token' do
|
|
249
|
+
expect { strategy.id_token_claims }.not_to raise_error
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
context 'with kid not found in JWKS' do
|
|
254
|
+
let(:jwt_with_unknown_kid) do
|
|
255
|
+
jwt = JSON::JWT.new(jwt_payload)
|
|
256
|
+
jwt.kid = 'unknown-kid'
|
|
257
|
+
jwt.sign(rsa_key, :RS256).to_s
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
before do
|
|
261
|
+
allow(access_token).to receive(:params).and_return({ 'id_token' => jwt_with_unknown_kid })
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
it 'raises an error' do
|
|
265
|
+
expect { strategy.id_token_claims }.to raise_error(StandardError)
|
|
266
|
+
end
|
|
86
267
|
end
|
|
87
268
|
end
|
|
88
269
|
|
|
@@ -146,6 +327,17 @@ RSpec.describe OmniAuth::Strategies::YahooJp do
|
|
|
146
327
|
expect(strategy.raw_info).to eq({})
|
|
147
328
|
end
|
|
148
329
|
end
|
|
330
|
+
|
|
331
|
+
context 'with userinfo_access: false and malformed id_token' do
|
|
332
|
+
before do
|
|
333
|
+
strategy.options[:userinfo_access] = false
|
|
334
|
+
allow(access_token).to receive(:params).and_return({ 'id_token' => 'not-a-jwt' })
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
it 'returns an empty hash instead of raising' do
|
|
338
|
+
expect(strategy.raw_info).to eq({})
|
|
339
|
+
end
|
|
340
|
+
end
|
|
149
341
|
end
|
|
150
342
|
|
|
151
343
|
describe '#info' do
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: omniauth-yahoojp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- mikanmarusan
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-06-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: omniauth
|
|
@@ -129,8 +129,6 @@ executables: []
|
|
|
129
129
|
extensions: []
|
|
130
130
|
extra_rdoc_files: []
|
|
131
131
|
files:
|
|
132
|
-
- ".github/workflows/claude-code-review.yml"
|
|
133
|
-
- ".github/workflows/claude.yml"
|
|
134
132
|
- ".gitignore"
|
|
135
133
|
- ".rspec"
|
|
136
134
|
- CLAUDE.md
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
name: Claude Code Review
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
pull_request:
|
|
5
|
-
types: [opened, synchronize]
|
|
6
|
-
# Optional: Only run on specific file changes
|
|
7
|
-
# paths:
|
|
8
|
-
# - "src/**/*.ts"
|
|
9
|
-
# - "src/**/*.tsx"
|
|
10
|
-
# - "src/**/*.js"
|
|
11
|
-
# - "src/**/*.jsx"
|
|
12
|
-
|
|
13
|
-
jobs:
|
|
14
|
-
claude-review:
|
|
15
|
-
# Optional: Filter by PR author
|
|
16
|
-
# if: |
|
|
17
|
-
# github.event.pull_request.user.login == 'external-contributor' ||
|
|
18
|
-
# github.event.pull_request.user.login == 'new-developer' ||
|
|
19
|
-
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
|
|
20
|
-
|
|
21
|
-
runs-on: ubuntu-latest
|
|
22
|
-
permissions:
|
|
23
|
-
contents: read
|
|
24
|
-
pull-requests: read
|
|
25
|
-
issues: read
|
|
26
|
-
id-token: write
|
|
27
|
-
|
|
28
|
-
steps:
|
|
29
|
-
- name: Checkout repository
|
|
30
|
-
uses: actions/checkout@v4
|
|
31
|
-
with:
|
|
32
|
-
fetch-depth: 1
|
|
33
|
-
|
|
34
|
-
- name: Run Claude Code Review
|
|
35
|
-
id: claude-review
|
|
36
|
-
uses: anthropics/claude-code-action@beta
|
|
37
|
-
with:
|
|
38
|
-
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
39
|
-
|
|
40
|
-
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
|
|
41
|
-
# model: "claude-opus-4-20250514"
|
|
42
|
-
|
|
43
|
-
# Direct prompt for automated review (no @claude mention needed)
|
|
44
|
-
direct_prompt: |
|
|
45
|
-
Please review this pull request and provide feedback on:
|
|
46
|
-
- Code quality and best practices
|
|
47
|
-
- Potential bugs or issues
|
|
48
|
-
- Performance considerations
|
|
49
|
-
- Security concerns
|
|
50
|
-
- Test coverage
|
|
51
|
-
|
|
52
|
-
Be constructive and helpful in your feedback.
|
|
53
|
-
|
|
54
|
-
# Optional: Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR
|
|
55
|
-
# use_sticky_comment: true
|
|
56
|
-
|
|
57
|
-
# Optional: Customize review based on file types
|
|
58
|
-
# direct_prompt: |
|
|
59
|
-
# Review this PR focusing on:
|
|
60
|
-
# - For TypeScript files: Type safety and proper interface usage
|
|
61
|
-
# - For API endpoints: Security, input validation, and error handling
|
|
62
|
-
# - For React components: Performance, accessibility, and best practices
|
|
63
|
-
# - For tests: Coverage, edge cases, and test quality
|
|
64
|
-
|
|
65
|
-
# Optional: Different prompts for different authors
|
|
66
|
-
# direct_prompt: |
|
|
67
|
-
# ${{ github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' &&
|
|
68
|
-
# 'Welcome! Please review this PR from a first-time contributor. Be encouraging and provide detailed explanations for any suggestions.' ||
|
|
69
|
-
# 'Please provide a thorough code review focusing on our coding standards and best practices.' }}
|
|
70
|
-
|
|
71
|
-
# Optional: Add specific tools for running tests or linting
|
|
72
|
-
# allowed_tools: "Bash(npm run test),Bash(npm run lint),Bash(npm run typecheck)"
|
|
73
|
-
|
|
74
|
-
# Optional: Skip review for certain conditions
|
|
75
|
-
# if: |
|
|
76
|
-
# !contains(github.event.pull_request.title, '[skip-review]') &&
|
|
77
|
-
# !contains(github.event.pull_request.title, '[WIP]')
|
|
78
|
-
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
name: Claude Code
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
issue_comment:
|
|
5
|
-
types: [created]
|
|
6
|
-
pull_request_review_comment:
|
|
7
|
-
types: [created]
|
|
8
|
-
issues:
|
|
9
|
-
types: [opened, assigned]
|
|
10
|
-
pull_request_review:
|
|
11
|
-
types: [submitted]
|
|
12
|
-
|
|
13
|
-
jobs:
|
|
14
|
-
claude:
|
|
15
|
-
if: |
|
|
16
|
-
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
|
17
|
-
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
|
18
|
-
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
|
19
|
-
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
|
20
|
-
runs-on: ubuntu-latest
|
|
21
|
-
permissions:
|
|
22
|
-
contents: read
|
|
23
|
-
pull-requests: read
|
|
24
|
-
issues: read
|
|
25
|
-
id-token: write
|
|
26
|
-
actions: read # Required for Claude to read CI results on PRs
|
|
27
|
-
steps:
|
|
28
|
-
- name: Checkout repository
|
|
29
|
-
uses: actions/checkout@v4
|
|
30
|
-
with:
|
|
31
|
-
fetch-depth: 1
|
|
32
|
-
|
|
33
|
-
- name: Run Claude Code
|
|
34
|
-
id: claude
|
|
35
|
-
uses: anthropics/claude-code-action@beta
|
|
36
|
-
with:
|
|
37
|
-
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
38
|
-
|
|
39
|
-
# This is an optional setting that allows Claude to read CI results on PRs
|
|
40
|
-
additional_permissions: |
|
|
41
|
-
actions: read
|
|
42
|
-
|
|
43
|
-
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
|
|
44
|
-
# model: "claude-opus-4-20250514"
|
|
45
|
-
|
|
46
|
-
# Optional: Customize the trigger phrase (default: @claude)
|
|
47
|
-
# trigger_phrase: "/claude"
|
|
48
|
-
|
|
49
|
-
# Optional: Trigger when specific user is assigned to an issue
|
|
50
|
-
# assignee_trigger: "claude-bot"
|
|
51
|
-
|
|
52
|
-
# Optional: Allow Claude to run specific commands
|
|
53
|
-
# allowed_tools: "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)"
|
|
54
|
-
|
|
55
|
-
# Optional: Add custom instructions for Claude to customize its behavior for your project
|
|
56
|
-
# custom_instructions: |
|
|
57
|
-
# Follow our coding standards
|
|
58
|
-
# Ensure all new code has tests
|
|
59
|
-
# Use TypeScript for new files
|
|
60
|
-
|
|
61
|
-
# Optional: Custom environment variables for Claude
|
|
62
|
-
# claude_env: |
|
|
63
|
-
# NODE_ENV: test
|
|
64
|
-
|