@@ -9,11 +9,13 @@ class AgreementRequiredError < StandardError; end
9
9
option state : SecureRandom . hex ( 24 )
10
10
option :pkce , true
11
11
12
+ SRF_GUARD_COOKIE_NAME = 'xsrf_guard' . freeze
13
+
12
14
uid { raw_info [ "user_id" ] }
13
15
14
16
info do
15
17
{
16
- nickname : raw_info [ "nickname" ] ,
18
+ user_id : raw_info [ 'user_id' ] ,
17
19
email : raw_info [ "user_email" ] ,
18
20
name : raw_info [ "nickname" ]
19
21
}
@@ -28,9 +30,38 @@ def raw_info
28
30
end
29
31
30
32
def callback_phase
31
- super
33
+
34
+ if cookie_store?
35
+ super
36
+ end
37
+
38
+ error = request . params [ "error_reason" ] || request . params [ "error" ]
39
+
40
+ # エラーチェック
41
+ if error
42
+ fail! ( error , CallbackError . new ( request . params [ "error" ] , request . params [ "error_description" ] || request . params [ "error_reason" ] , request . params [ "error_uri" ] ) )
43
+ elsif request . params [ 'code' ]
44
+ super
45
+ elsif request . cookies [ SRF_GUARD_COOKIE_NAME ] && session [ :csrf_guard ]
46
+
47
+ # オプトインフローでのCSRFチェック
48
+ handle_opt_in_flow_with_csrf_check
49
+ OmniAuth ::Strategy . instance_method ( :callback_phase ) . bind ( self ) . call
50
+ else
51
+ # 期待されるパラメータがない場合のエラー処理
52
+ fail! ( :invalid_request , CallbackError . new ( :invalid_request , "Invalid request in callback" ) )
53
+ end
32
54
rescue AgreementRequiredError => e
33
- return redirect ( e . message )
55
+ if cookie_store?
56
+ return redirect ( e . message )
57
+ end
58
+ return redirect_with_csrf_token ( e . message )
59
+ rescue ::OAuth2 ::Error , CallbackError => e
60
+ fail! ( :invalid_credentials , e )
61
+ rescue ::Timeout ::Error , ::Errno ::ETIMEDOUT => e
62
+ fail! ( :timeout , e )
63
+ rescue ::SocketError => e
64
+ fail! ( :failed_to_connect , e )
34
65
end
35
66
36
67
def setup_phase
@@ -83,8 +114,15 @@ def user_info_opt
83
114
}
84
115
} . to_json
85
116
) . parsed
117
+ session [ "access_token" ] = access_token . token
86
118
if @id_token_payload . agreeFlg == "0" then
87
- agreement_url = "https://#{ options . optin_url } ?serviceId=#{ options . service_id } &redirectUrl=#{ CGI . escape ( agreement_callback_url ) } "
119
+ redirect_url = nil
120
+ if cookie_store?
121
+ redirect_url = agreement_callback_url
122
+ else
123
+ redirect_url = callback_url
124
+ end
125
+ agreement_url = "https://#{ options . optin_url } ?serviceId=#{ options . service_id } &redirectUrl=#{ CGI . escape ( redirect_url ) } "
88
126
raise AgreementRequiredError , agreement_url
89
127
else
90
128
Rails . logger . info ( "token:#{ @id_token_payload . inspect } " )
@@ -106,6 +144,111 @@ def site_url
106
144
return parts [ 0 ] + '.com' if parts . size > 1
107
145
url
108
146
end
147
+
148
+ def handle_opt_in_flow_with_csrf_check
149
+ csrf_token = request . cookies [ SRF_GUARD_COOKIE_NAME ]
150
+
151
+ if csrf_token . nil? || csrf_token != session [ :csrf_guard ]
152
+ fail! ( :csrf_detected , CallbackError . new ( :csrf_detected , "CSRF detected" ) )
153
+ else
154
+ # CSRFトークンが一致した場合、セッションとクッキーからCSRFトークンを削除
155
+ session . delete ( :csrf_guard )
156
+ session . delete ( "omniauth.pkce.verifier" )
157
+ request . cookies . delete ( SRF_GUARD_COOKIE_NAME )
158
+ # オプトイン承認後に必要な処理を行う
159
+ request . params [ 'state' ] = csrf_token
160
+ session [ 'omniauth.state' ] = csrf_token
161
+ Rails . logger . debug "Current sessions: #{ session . to_hash . inspect } "
162
+ self . access_token = build_access_token_class session [ "access_token" ]
163
+ end
164
+ end
165
+
166
+ def redirect_with_csrf_token ( uri , options = { } )
167
+ r = Rack ::Response . new
168
+
169
+ # URIからパスを抽出
170
+ begin
171
+ parsed_uri = URI ( uri )
172
+ path = parsed_uri . path . empty? ? '/' : parsed_uri . path
173
+ domain = extract_domain ( parsed_uri . host )
174
+ rescue URI ::InvalidURIError
175
+ # 無効なURIの場合のエラー処理
176
+ Rails . logger . error "Invalid URI provided: #{ uri } "
177
+ raise ArgumentError , "Invalid URI provided: #{ uri } "
178
+ end
179
+
180
+
181
+ csrf_token = SecureRandom . urlsafe_base64 ( 32 )
182
+ session [ :csrf_guard ] = csrf_token
183
+
184
+ r . set_cookie ( SRF_GUARD_COOKIE_NAME , {
185
+ value : csrf_token ,
186
+ expires : 5 . minutes . from_now ,
187
+ secure : true ,
188
+ http_only : true ,
189
+ same_site : :none # クロスサイトリクエストを許可する場合
190
+ } )
191
+
192
+ Rails . logger . debug "Cookies after setting:"
193
+ Rails . logger . debug r . headers [ "Set-Cookie" ]
194
+
195
+ if options [ :iframe ]
196
+ r . write ( "<script type='text/javascript' charset='utf-8'>top.location.href = '#{ uri . to_json } ';</script>" )
197
+ else
198
+ # r.write("Redirecting to #{uri}...")
199
+ r . redirect ( uri )
200
+ end
201
+
202
+ r . finish
203
+ end
204
+
205
+ def extract_domain ( host )
206
+ return nil if host . nil?
207
+
208
+ parts = host . split ( '.' )
209
+ return host if parts . length <= 2
210
+
211
+ # サブドメインを除去し、メインドメインとTLDを返す
212
+ parts . slice ( -2 , 2 ) . join ( '.' )
213
+ end
214
+
215
+ def build_access_token_class ( existing_token )
216
+ ::OAuth2 ::AccessToken . new (
217
+ client ,
218
+ existing_token
219
+ )
220
+ end
221
+
222
+ def sanitize_nickname ( nickname )
223
+ # 正規表現パターン: 文字、数字、'-' および '_' のみを許可
224
+ valid_pattern = /^[a-zA-Z0-9\- _]+$/
225
+
226
+ if nickname . present? && nickname . match? ( valid_pattern )
227
+ nickname
228
+ else
229
+ # ランダムな8文字の英数字を生成
230
+ SecureRandom . alphanumeric ( 8 )
231
+ end
232
+ end
233
+
234
+ def detect_session_store
235
+ return false unless defined? ( Rails )
236
+ return false unless Rails . application
237
+
238
+ begin
239
+ store = Rails . application . config . session_store
240
+ return store if store
241
+ false
242
+ rescue
243
+ false
244
+ end
245
+ end
246
+
247
+ def cookie_store?
248
+ store = detect_session_store
249
+ return true unless store # セッションが検出できない場合はcookie扱い
250
+ store . to_s . include? ( 'CookieStore' )
251
+ end
109
252
end
110
253
end
111
254
end
0 commit comments