omniauth-yahoojp 0.2.0 → 1.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ca264c63b14fa89691cfe5a8e11f23cde931c0a4
4
- data.tar.gz: adf2273ad2d2d2b3af15fd3e2b68503daf9c7d4e
2
+ SHA256:
3
+ metadata.gz: 16ae4d397a52eca91f5a45f36770e3ec1162bda6f607a1c2bbd5142ee9799b9c
4
+ data.tar.gz: 7989bc85f42e4ae4e6b2420e5d3a6c94355a87db5ba0997b1a1a8a62249ae088
5
5
  SHA512:
6
- metadata.gz: 8acdf1219c6775476415c42f5914e3afc16ab94ad28977c87183a736efafc689a13ec3156075a5a979b9eadbac25f9f4618bcc799a50c3bcf20a5fa458484775
7
- data.tar.gz: 3a53191f69d16ff6442d88297630d320e5251b75cced5ff31d7f796cd92b4d059f92933a57ad24baef25a11cf6a2b11c4c0db5cb781eb9b1c3e20f232ee33b71
6
+ metadata.gz: c6f857f7d5604801d3aff235992b49d925a889e6ca6458400ceb746abe617594a8fa5ab229312f332f34df6fae83d3af508b33f39fd5d3c93bf4a611884395d9
7
+ data.tar.gz: 8c2a1ca936c8c89adbd4655d01021e14a7e669965be3015fd028df1bcb0c82164a66b90f116056c9d21963f6d5b22ae0ca3329ea9e13001336149f921f344be0
@@ -0,0 +1,78 @@
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
+
@@ -0,0 +1,64 @@
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
+
data/CLAUDE.md ADDED
@@ -0,0 +1,49 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Development Commands
6
+
7
+ ### Testing
8
+ - `rake spec` or `rspec` - Run all tests
9
+ - `rspec spec/omniauth/strategies/yahoojp_spec.rb` - Run specific test file
10
+ - `rake` - Default task runs specs
11
+
12
+ ### Development Tools
13
+ - `guard` - File watcher for automatic test running (configured via Guardfile)
14
+ - `bundle install` - Install gem dependencies
15
+
16
+ ## Architecture
17
+
18
+ This is a Ruby gem that implements an OmniAuth strategy for Yahoo! JAPAN's YConnect OAuth 2.0 service.
19
+
20
+ ### Core Components
21
+
22
+ **Main Strategy**: `lib/omniauth/strategies/yahoojp.rb`
23
+ - Inherits from `OmniAuth::Strategies::OAuth2`
24
+ - Implements Yahoo! JAPAN YConnect v2 API endpoints
25
+ - Handles OAuth 2.0 authorization code flow with basic auth for token exchange
26
+ - Supports YConnect-specific parameters: display, prompt, scope, bail
27
+
28
+ **Key Implementation Details**:
29
+ - Uses `https://auth.login.yahoo.co.jp` as OAuth provider
30
+ - YConnect v2 endpoints: `/yconnect/v2/authorization` and `/yconnect/v2/token`
31
+ - User info endpoint: `https://userinfo.yahooapis.jp/yconnect/v2/attribute`
32
+ - Custom `build_access_token` method for Yahoo-specific auth headers
33
+ - Supports Japanese locale-specific fields (kana, kanji names)
34
+
35
+ ### File Structure
36
+ - `lib/omniauth-yahoojp.rb` - Main entry point, requires the strategy
37
+ - `lib/omniauth-yahoojp/version.rb` - Gem version
38
+ - `lib/omniauth/strategies/yahoojp.rb` - Strategy implementation
39
+ - `spec/` - RSpec tests
40
+
41
+ ### Dependencies
42
+ - `omniauth` and `omniauth-oauth2` for OAuth framework
43
+ - `httpauth` for HTTP basic authentication headers
44
+
45
+ ## Downstream Test Project
46
+
47
+ - [`omniauth-yahoojp-tester-rails5`](https://github.com/mikanmarusan/omniauth-yahoojp-tester-rails5) is a Rails 5 app that consumes this gem for integration testing.
48
+ - Its CLAUDE.md documents the exact API surface used and cross-project update rules.
49
+ - Use `/add-dir ../omniauth-yahoojp-tester-rails5` to add it for cross-project awareness when making API changes.
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source 'http://rubygems.org'
1
+ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in omniauth-yahoojp.gemspec
4
4
  gemspec
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **These notes are based on master, please see tags for README pertaining to specific releases.**
4
4
 
5
- This is the official OmniAuth strategy for authenticating to Yahoo! JAPAN( [YConnect](http://developer.yahoo.co.jp/yconnect/v2/) ).
5
+ This is the OmniAuth strategy for authenticating to Yahoo! JAPAN( [YConnect](http://developer.yahoo.co.jp/yconnect/v2/) ).
6
6
  To use it, you'll need to sign up for a YConnect Client ID and Secret
7
7
  on the [Yahoo! JAPAN Developer Network](https://e.developer.yahoo.co.jp/dashboard/).
8
8
 
@@ -22,13 +22,14 @@ Then `bundle install`.
22
22
 
23
23
  `OmniAuth::Strategies::YahooJp` is simply a Rack middleware. Read the OmniAuth docs for detailed instructions: https://github.com/intridea/omniauth.
24
24
 
25
- YConnect API v2 lets you set scopes to provide granular access to different types of data:
25
+ YConnect API v2 lets you set scopes to provide granular access to different types of data:
26
26
 
27
27
  ```ruby
28
28
  Rails.application.config.middleware.use OmniAuth::Builder do
29
- provider :yahoojp, ENV['YAHOOJP_KEY'], ENV['YAHOOJP_SECRET'],
29
+ provider :yahoojp, ENV['YAHOOJP_KEY'], ENV['YAHOOJP_SECRET'],
30
30
  {
31
- scope: "openid profile email address"
31
+ scope: "openid profile email address",
32
+ userinfo_access: true # default: true
32
33
  }
33
34
  end
34
35
  ```
@@ -50,6 +51,24 @@ Rails.application.config.middleware.use OmniAuth::Builder do
50
51
  end
51
52
  ```
52
53
 
54
+ ### `userinfo_access` Option
55
+
56
+ Controls how user profile information is retrieved.
57
+
58
+ - `userinfo_access: true` (default) — Calls the UserInfo API (`https://userinfo.yahooapis.jp/yconnect/v2/attribute`) to retrieve full profile information including name variants (kana, kanji), gender, locale, birthdate, nickname, picture, email, and address.
59
+ - `userinfo_access: false` — Skips the UserInfo API call and extracts profile information from the `id_token` claims instead. This is useful when your application does not have access to the UserInfo API.
60
+
61
+ > **Note:** The fields available in `id_token` claims may differ from those returned by the UserInfo API. The `id_token` typically contains a subset of profile fields depending on the requested scopes. Yahoo! JAPAN has made the UserInfo API access-restricted, so some applications may need to rely on `id_token` claims.
62
+
63
+ ### ID Token
64
+
65
+ When the `openid` scope is requested, the strategy automatically captures the `id_token` returned by Yahoo! JAPAN's token endpoint.
66
+
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`.
69
+
70
+ The `id_token` signature verification is skipped because the token is received directly from Yahoo! JAPAN's token endpoint over TLS in the Authorization Code Flow, which guarantees its authenticity.
71
+
53
72
  ### API Version
54
73
 
55
74
  OmniAuth YahooJp uses versioned API endpoints by default (current v2). You can configure a different version via `client_options` hash passed to `provider`, specifically you should change the version in the `site` and `authorize_url` parameters. For example, to change to v1:
@@ -60,7 +79,6 @@ Rails.application.config.middleware.use OmniAuth::Builder do
60
79
  {
61
80
  scope: "openid profile email address",
62
81
  client_options: {
63
- site: 'https://auth.login.yahoo.co.jp',
64
82
  authorize_url: '/yconnect/v1/authorization',
65
83
  token_url: '/yconnect/v1/token'
66
84
  }
@@ -68,6 +86,52 @@ Rails.application.config.middleware.use OmniAuth::Builder do
68
86
  end
69
87
  ```
70
88
 
89
+ ## Auth Hash
90
+
91
+ Here is an example of the auth hash available in `request.env['omniauth.auth']`:
92
+
93
+ ```ruby
94
+ {
95
+ provider: "yahoojp",
96
+ uid: "abcdefg",
97
+ info: {
98
+ sub: "abcdefg",
99
+ name: "山田 太郎",
100
+ given_name: "太郎",
101
+ given_name_ja_kana_jp: "タロウ",
102
+ given_name_ja_hani_jp: "太郎",
103
+ family_name: "山田",
104
+ family_name_ja_kana_jp: "ヤマダ",
105
+ family_name_ja_hani_jp: "山田",
106
+ gender: "male",
107
+ locale: "ja-JP",
108
+ email: "example@yahoo.co.jp",
109
+ email_verified: true
110
+ },
111
+ credentials: {
112
+ token: "ACCESS_TOKEN",
113
+ refresh_token: "REFRESH_TOKEN",
114
+ expires_at: 1496120719,
115
+ expires: true,
116
+ id_token: "eyJhbGciOiJSUzI1NiIs..."
117
+ },
118
+ extra: {
119
+ raw_info: { ... }, # Full response from UserInfo API (or id_token claims)
120
+ id_token: "eyJhbGciOiJSUzI1NiIs...", # Raw JWT string
121
+ id_token_claims: { # Decoded id_token claims
122
+ iss: "https://auth.login.yahoo.co.jp/yconnect/v2",
123
+ sub: "abcdefg",
124
+ aud: ["YOUR_CLIENT_ID"],
125
+ exp: 1496120719,
126
+ iat: 1496117119,
127
+ nonce: "..."
128
+ }
129
+ }
130
+ }
131
+ ```
132
+
133
+ > **Note:** Available fields in `info` and `extra.raw_info` depend on the requested scopes and the `userinfo_access` setting.
134
+
71
135
  ## License
72
136
 
73
137
  Copyright (c) 2013 by mikanmarusan
@@ -1,5 +1,6 @@
1
1
  require 'omniauth-oauth2'
2
2
  require 'httpauth'
3
+ require 'json/jwt'
3
4
 
4
5
  module OmniAuth
5
6
  module Strategies
@@ -14,10 +15,7 @@ module OmniAuth
14
15
  }
15
16
 
16
17
  option :authorize_options, [:display, :prompt, :scope, :bail]
17
-
18
- def request_phase
19
- super
20
- end
18
+ option :userinfo_access, true
21
19
 
22
20
  uid { raw_info['sub'] }
23
21
 
@@ -39,19 +37,47 @@ module OmniAuth
39
37
  :picture => raw_info['picture'],
40
38
  :email => raw_info['email'],
41
39
  :email_verified => raw_info['email_verified'],
42
- :address => raw_info['address'],
40
+ :address => raw_info['address'],
43
41
  })
44
42
  end
45
43
 
46
44
  extra do
47
45
  hash = {}
48
46
  hash[:raw_info] = raw_info unless skip_info?
47
+ hash[:id_token] = id_token if id_token
48
+ hash[:id_token_claims] = id_token_claims if id_token
49
49
  prune! hash
50
50
  end
51
51
 
52
+ credentials do
53
+ hash = {'token' => access_token.token}
54
+ hash['refresh_token'] = access_token.refresh_token if access_token.refresh_token
55
+ hash['expires_at'] = access_token.expires_at if access_token.expires?
56
+ hash['expires'] = access_token.expires?
57
+ hash['id_token'] = id_token if id_token
58
+ hash
59
+ end
60
+
52
61
  def raw_info
53
- access_token.options[:mode] = :header
54
- @raw_info ||= access_token.get('https://userinfo.yahooapis.jp/yconnect/v2/attribute').parsed
62
+ @raw_info ||= if options.userinfo_access
63
+ access_token.options[:mode] = :header
64
+ access_token.get('https://userinfo.yahooapis.jp/yconnect/v2/attribute').parsed
65
+ elsif id_token
66
+ id_token_claims
67
+ else
68
+ {}
69
+ end
70
+ end
71
+
72
+ def id_token
73
+ @id_token ||= access_token&.params&.dig('id_token').presence
74
+ end
75
+
76
+ def id_token_claims
77
+ return nil unless id_token
78
+ # Signature verification is skipped because the id_token was received
79
+ # directly from Yahoo's token endpoint over TLS (Authorization Code Flow).
80
+ @id_token_claims ||= JSON::JWT.decode(id_token, :skip_verification)
55
81
  end
56
82
 
57
83
  def prune!(hash)
@@ -69,7 +95,7 @@ module OmniAuth
69
95
  :headers => {'Authorization' => HTTPAuth::Basic.pack_authorization(client.id, client.secret)}
70
96
  }
71
97
 
72
- client.get_token(token_params);
98
+ client.get_token(token_params)
73
99
  end
74
100
 
75
101
  def callback_url
@@ -1,5 +1,5 @@
1
1
  module OmniAuth
2
2
  module YahooJp
3
- VERSION = "0.2.0"
3
+ VERSION = "1.0.0"
4
4
  end
5
5
  end
@@ -4,8 +4,8 @@ require File.expand_path('../lib/omniauth-yahoojp/version', __FILE__)
4
4
  Gem::Specification.new do |gem|
5
5
  gem.authors = ["mikanmarusan"]
6
6
  gem.email = ["chiakifujimon@gmail.com"]
7
- gem.description = %q{Official OmniAuth strategy for Yahoo! JAPAN.}
8
- gem.summary = %q{Official OmniAuth strategy for Yahoo! JAPAN.}
7
+ gem.description = %q{OmniAuth strategy for Yahoo! JAPAN.}
8
+ gem.summary = %q{OmniAuth strategy for Yahoo! JAPAN.}
9
9
  gem.homepage = "https://github.com/mikanmarusan/omniauth-yahoojp"
10
10
 
11
11
  gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
@@ -14,12 +14,13 @@ Gem::Specification.new do |gem|
14
14
  gem.name = "omniauth-yahoojp"
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = OmniAuth::YahooJp::VERSION
17
- gem.licenses = "MIT"
17
+ gem.licenses = ["MIT"]
18
18
 
19
- gem.add_dependency 'omniauth', '~> 1.0'
20
- gem.add_dependency 'omniauth-oauth2', '~> 1.1'
19
+ gem.add_dependency 'omniauth', '>= 1.0'
20
+ gem.add_dependency 'omniauth-oauth2', '>= 1.1'
21
21
  gem.add_dependency 'httpauth'
22
- gem.add_development_dependency 'rspec', '~> 2.7'
22
+ gem.add_dependency 'json-jwt', '>= 1.16.6'
23
+ gem.add_development_dependency 'rspec', '~> 3.0'
23
24
  gem.add_development_dependency 'rack-test'
24
25
  gem.add_development_dependency 'simplecov'
25
26
  gem.add_development_dependency 'webmock'
@@ -1,23 +1,289 @@
1
1
  require 'spec_helper'
2
2
  require 'omniauth-yahoojp'
3
+ require 'json/jwt'
3
4
 
4
- describe OmniAuth::Strategies::YahooJp do
5
+ RSpec.describe OmniAuth::Strategies::YahooJp do
6
+ let(:app) { lambda { |_env| [200, {}, ['Hello']] } }
7
+ let(:strategy) { described_class.new(app, 'client_id', 'client_secret') }
5
8
 
6
- subject do
7
- OmniAuth::Strategies::YahooJp.new({})
9
+ describe 'client options' do
10
+ it 'has correct site' do
11
+ expect(strategy.options.client_options.site).to eq('https://auth.login.yahoo.co.jp')
12
+ end
13
+
14
+ it 'has correct authorize url' do
15
+ expect(strategy.options.client_options.authorize_url).to eq('/yconnect/v2/authorization')
16
+ end
17
+
18
+ it 'has correct token url' do
19
+ expect(strategy.options.client_options.token_url).to eq('/yconnect/v2/token')
20
+ end
8
21
  end
9
22
 
10
- context "client options" do
11
- it 'should have correct site' do
12
- subject.options.client_options.site.should eq("https://auth.login.yahoo.co.jp")
23
+ describe 'default options' do
24
+ it 'has userinfo_access enabled by default' do
25
+ expect(strategy.options.userinfo_access).to be true
26
+ end
27
+ end
28
+
29
+ context 'with access_token' do
30
+ let(:jwt_payload) { { 'sub' => 'test123', 'name' => 'Test User', 'email' => 'test@example.com' } }
31
+ let(:jwt_string) { JSON::JWT.new(jwt_payload).to_s }
32
+ let(:access_token) do
33
+ instance_double(
34
+ OAuth2::AccessToken,
35
+ token: 'mock_token',
36
+ refresh_token: 'mock_refresh',
37
+ expires?: true,
38
+ expires_at: 1234567890,
39
+ params: { 'id_token' => jwt_string },
40
+ options: {}
41
+ )
42
+ end
43
+
44
+ before do
45
+ allow(strategy).to receive(:access_token).and_return(access_token)
46
+ end
47
+
48
+ describe '#id_token' do
49
+ it 'returns the id_token from access_token params' do
50
+ expect(strategy.id_token).to eq(jwt_string)
51
+ end
52
+
53
+ it 'returns nil when no id_token in params' do
54
+ allow(access_token).to receive(:params).and_return({})
55
+ expect(strategy.id_token).to be_nil
56
+ end
57
+
58
+ it 'returns nil when id_token is empty string' do
59
+ allow(access_token).to receive(:params).and_return({ 'id_token' => '' })
60
+ expect(strategy.id_token).to be_nil
61
+ end
13
62
  end
14
63
 
15
- it 'should have correct authorize url' do
16
- subject.options.client_options.authorize_url.should eq('/yconnect/v1/authorization')
64
+ describe '#id_token_claims' do
65
+ it 'decodes the JWT without verification' do
66
+ claims = strategy.id_token_claims
67
+ expect(claims['sub']).to eq('test123')
68
+ expect(claims['name']).to eq('Test User')
69
+ expect(claims['email']).to eq('test@example.com')
70
+ end
71
+
72
+ it 'memoizes the result' do
73
+ first_call = strategy.id_token_claims
74
+ second_call = strategy.id_token_claims
75
+ expect(first_call).to equal(second_call)
76
+ end
77
+
78
+ it 'returns nil when id_token is nil' do
79
+ allow(access_token).to receive(:params).and_return({})
80
+ expect(strategy.id_token_claims).to be_nil
81
+ end
82
+
83
+ it 'raises on malformed id_token' do
84
+ allow(access_token).to receive(:params).and_return({ 'id_token' => 'not-a-jwt' })
85
+ expect { strategy.id_token_claims }.to raise_error(JSON::JWT::InvalidFormat)
86
+ end
17
87
  end
18
88
 
19
- it 'should have correct token url' do
20
- subject.options.client_options.token_url.should eq('/yconnect/v1/token')
89
+ describe '#raw_info' do
90
+ context 'with userinfo_access: true' do
91
+ let(:userinfo_response) do
92
+ instance_double(OAuth2::Response, parsed: {
93
+ 'sub' => 'user456',
94
+ 'name' => 'Yahoo User',
95
+ 'email' => 'yahoo@example.com',
96
+ 'address' => { 'country' => 'JP' }
97
+ })
98
+ end
99
+
100
+ before do
101
+ allow(access_token).to receive(:get)
102
+ .with('https://userinfo.yahooapis.jp/yconnect/v2/attribute')
103
+ .and_return(userinfo_response)
104
+ end
105
+
106
+ it 'calls the UserInfo API' do
107
+ strategy.raw_info
108
+ expect(access_token).to have_received(:get)
109
+ .with('https://userinfo.yahooapis.jp/yconnect/v2/attribute')
110
+ end
111
+
112
+ it 'sets access_token mode to header' do
113
+ strategy.raw_info
114
+ expect(access_token.options[:mode]).to eq(:header)
115
+ end
116
+
117
+ it 'returns parsed UserInfo response' do
118
+ expect(strategy.raw_info['sub']).to eq('user456')
119
+ expect(strategy.raw_info['name']).to eq('Yahoo User')
120
+ end
121
+ end
122
+
123
+ context 'with userinfo_access: false and id_token present' do
124
+ before do
125
+ strategy.options[:userinfo_access] = false
126
+ end
127
+
128
+ it 'returns id_token_claims' do
129
+ expect(strategy.raw_info['sub']).to eq('test123')
130
+ expect(strategy.raw_info['name']).to eq('Test User')
131
+ end
132
+
133
+ it 'does not call the UserInfo API' do
134
+ expect(access_token).not_to receive(:get)
135
+ strategy.raw_info
136
+ end
137
+ end
138
+
139
+ context 'with userinfo_access: false and no id_token' do
140
+ before do
141
+ strategy.options[:userinfo_access] = false
142
+ allow(access_token).to receive(:params).and_return({})
143
+ end
144
+
145
+ it 'returns empty hash' do
146
+ expect(strategy.raw_info).to eq({})
147
+ end
148
+ end
149
+ end
150
+
151
+ describe '#info' do
152
+ let(:full_profile) do
153
+ {
154
+ 'sub' => 'user456',
155
+ 'name' => 'Yahoo User',
156
+ 'given_name' => 'Taro',
157
+ 'given_name#ja-Kana-JP' => 'タロウ',
158
+ 'given_name#ja-Hani-JP' => '太郎',
159
+ 'family_name' => 'Yamada',
160
+ 'family_name#ja-Kana-JP' => 'ヤマダ',
161
+ 'family_name#ja-Hani-JP' => '山田',
162
+ 'gender' => 'male',
163
+ 'zoneinfo' => 'Asia/Tokyo',
164
+ 'locale' => 'ja-JP',
165
+ 'birthdate' => '1990-01-01',
166
+ 'nickname' => 'taro',
167
+ 'picture' => 'https://example.com/photo.jpg',
168
+ 'email' => 'taro@example.com',
169
+ 'email_verified' => true,
170
+ 'address' => { 'country' => 'JP', 'region' => 'Tokyo' }
171
+ }
172
+ end
173
+ let(:userinfo_response) do
174
+ instance_double(OAuth2::Response, parsed: full_profile)
175
+ end
176
+
177
+ before do
178
+ allow(access_token).to receive(:get)
179
+ .with('https://userinfo.yahooapis.jp/yconnect/v2/attribute')
180
+ .and_return(userinfo_response)
181
+ end
182
+
183
+ it 'maps all standard fields' do
184
+ info = strategy.info
185
+ expect(info[:sub]).to eq('user456')
186
+ expect(info[:name]).to eq('Yahoo User')
187
+ expect(info[:given_name]).to eq('Taro')
188
+ expect(info[:family_name]).to eq('Yamada')
189
+ expect(info[:email]).to eq('taro@example.com')
190
+ expect(info[:nickname]).to eq('taro')
191
+ expect(info[:picture]).to eq('https://example.com/photo.jpg')
192
+ end
193
+
194
+ it 'maps Japanese locale-specific fields' do
195
+ info = strategy.info
196
+ expect(info[:given_name_ja_kana_jp]).to eq('タロウ')
197
+ expect(info[:given_name_ja_hani_jp]).to eq('太郎')
198
+ expect(info[:family_name_ja_kana_jp]).to eq('ヤマダ')
199
+ expect(info[:family_name_ja_hani_jp]).to eq('山田')
200
+ end
201
+
202
+ it 'maps address as nested hash' do
203
+ info = strategy.info
204
+ expect(info[:address]['country']).to eq('JP')
205
+ end
206
+
207
+ it 'prunes nil values' do
208
+ allow(userinfo_response).to receive(:parsed).and_return({ 'sub' => 'user456' })
209
+ info = strategy.info
210
+ expect(info).not_to have_key(:name)
211
+ expect(info).not_to have_key(:email)
212
+ end
213
+ end
214
+
215
+ describe '#uid' do
216
+ context 'with userinfo_access: true' do
217
+ let(:userinfo_response) do
218
+ instance_double(OAuth2::Response, parsed: { 'sub' => 'user456' })
219
+ end
220
+
221
+ before do
222
+ allow(access_token).to receive(:get)
223
+ .with('https://userinfo.yahooapis.jp/yconnect/v2/attribute')
224
+ .and_return(userinfo_response)
225
+ end
226
+
227
+ it 'returns sub from UserInfo' do
228
+ expect(strategy.uid).to eq('user456')
229
+ end
230
+ end
231
+
232
+ context 'with userinfo_access: false' do
233
+ before do
234
+ strategy.options[:userinfo_access] = false
235
+ end
236
+
237
+ it 'returns sub from id_token' do
238
+ expect(strategy.uid).to eq('test123')
239
+ end
240
+ end
241
+ end
242
+
243
+ describe '#credentials' do
244
+ it 'includes token' do
245
+ expect(strategy.credentials['token']).to eq('mock_token')
246
+ end
247
+
248
+ it 'includes refresh_token' do
249
+ expect(strategy.credentials['refresh_token']).to eq('mock_refresh')
250
+ end
251
+
252
+ it 'includes expires_at' do
253
+ expect(strategy.credentials['expires_at']).to eq(1234567890)
254
+ end
255
+
256
+ it 'includes expires flag' do
257
+ expect(strategy.credentials['expires']).to be true
258
+ end
259
+
260
+ it 'includes id_token' do
261
+ expect(strategy.credentials['id_token']).to eq(jwt_string)
262
+ end
263
+
264
+ it 'omits id_token when not present' do
265
+ allow(access_token).to receive(:params).and_return({})
266
+ expect(strategy.credentials).not_to have_key('id_token')
267
+ end
268
+ end
269
+
270
+ describe '#extra' do
271
+ before do
272
+ strategy.options[:userinfo_access] = false
273
+ end
274
+
275
+ it 'includes id_token' do
276
+ expect(strategy.extra[:id_token]).to eq(jwt_string)
277
+ end
278
+
279
+ it 'includes id_token_claims' do
280
+ claims = strategy.extra[:id_token_claims]
281
+ expect(claims['sub']).to eq('test123')
282
+ end
283
+
284
+ it 'includes raw_info' do
285
+ expect(strategy.extra[:raw_info]).not_to be_nil
286
+ end
21
287
  end
22
288
  end
23
289
  end
data/spec/spec_helper.rb CHANGED
@@ -11,6 +11,4 @@ require 'omniauth-yahoojp'
11
11
  RSpec.configure do |config|
12
12
  config.include WebMock::API
13
13
  config.include Rack::Test::Methods
14
- config.extend OmniAuth::Test::StrategyMacros, :type => :strategy
15
14
  end
16
-
metadata CHANGED
@@ -1,122 +1,139 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omniauth-yahoojp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - mikanmarusan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-02 00:00:00.000000000 Z
11
+ date: 2026-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: omniauth
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: omniauth-oauth2
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '1.1'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.1'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: httpauth
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: json-jwt
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 1.16.6
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 1.16.6
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rspec
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
- - - ~>
73
+ - - "~>"
60
74
  - !ruby/object:Gem::Version
61
- version: '2.7'
75
+ version: '3.0'
62
76
  type: :development
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
- - - ~>
80
+ - - "~>"
67
81
  - !ruby/object:Gem::Version
68
- version: '2.7'
82
+ version: '3.0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: rack-test
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
- - - '>='
87
+ - - ">="
74
88
  - !ruby/object:Gem::Version
75
89
  version: '0'
76
90
  type: :development
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
- - - '>='
94
+ - - ">="
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: simplecov
85
99
  requirement: !ruby/object:Gem::Requirement
86
100
  requirements:
87
- - - '>='
101
+ - - ">="
88
102
  - !ruby/object:Gem::Version
89
103
  version: '0'
90
104
  type: :development
91
105
  prerelease: false
92
106
  version_requirements: !ruby/object:Gem::Requirement
93
107
  requirements:
94
- - - '>='
108
+ - - ">="
95
109
  - !ruby/object:Gem::Version
96
110
  version: '0'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: webmock
99
113
  requirement: !ruby/object:Gem::Requirement
100
114
  requirements:
101
- - - '>='
115
+ - - ">="
102
116
  - !ruby/object:Gem::Version
103
117
  version: '0'
104
118
  type: :development
105
119
  prerelease: false
106
120
  version_requirements: !ruby/object:Gem::Requirement
107
121
  requirements:
108
- - - '>='
122
+ - - ">="
109
123
  - !ruby/object:Gem::Version
110
124
  version: '0'
111
- description: Official OmniAuth strategy for Yahoo! JAPAN.
125
+ description: OmniAuth strategy for Yahoo! JAPAN.
112
126
  email:
113
127
  - chiakifujimon@gmail.com
114
128
  executables: []
115
129
  extensions: []
116
130
  extra_rdoc_files: []
117
131
  files:
118
- - .gitignore
119
- - .rspec
132
+ - ".github/workflows/claude-code-review.yml"
133
+ - ".github/workflows/claude.yml"
134
+ - ".gitignore"
135
+ - ".rspec"
136
+ - CLAUDE.md
120
137
  - Gemfile
121
138
  - Guardfile
122
139
  - README.md
@@ -137,20 +154,19 @@ require_paths:
137
154
  - lib
138
155
  required_ruby_version: !ruby/object:Gem::Requirement
139
156
  requirements:
140
- - - '>='
157
+ - - ">="
141
158
  - !ruby/object:Gem::Version
142
159
  version: '0'
143
160
  required_rubygems_version: !ruby/object:Gem::Requirement
144
161
  requirements:
145
- - - '>='
162
+ - - ">="
146
163
  - !ruby/object:Gem::Version
147
164
  version: '0'
148
165
  requirements: []
149
- rubyforge_project:
150
- rubygems_version: 2.0.14.1
166
+ rubygems_version: 3.0.3.1
151
167
  signing_key:
152
168
  specification_version: 4
153
- summary: Official OmniAuth strategy for Yahoo! JAPAN.
169
+ summary: OmniAuth strategy for Yahoo! JAPAN.
154
170
  test_files:
155
171
  - spec/omniauth/strategies/yahoojp_spec.rb
156
172
  - spec/spec_helper.rb