aven 0.0.1 → 0.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/Rakefile +1 -1
- data/app/components/aven/views/oauth/error/component.html.erb +44 -0
- data/app/components/aven/views/oauth/error/component.rb +30 -0
- data/app/components/aven/views/static/index/component.html.erb +4 -4
- data/app/components/aven/views/static/index/component.rb +11 -0
- data/app/controllers/aven/admin/base.rb +4 -4
- data/app/controllers/aven/application_controller.rb +22 -0
- data/app/controllers/aven/auth_controller.rb +6 -58
- data/app/controllers/aven/oauth/auth0_controller.rb +84 -0
- data/app/controllers/aven/oauth/base_controller.rb +183 -0
- data/app/controllers/aven/oauth/documentation/auth0.md +387 -0
- data/app/controllers/aven/oauth/documentation/entra_id.md +608 -0
- data/app/controllers/aven/oauth/documentation/github.md +329 -0
- data/app/controllers/aven/oauth/documentation/google.md +253 -0
- data/app/controllers/aven/oauth/entra_id_controller.rb +92 -0
- data/app/controllers/aven/oauth/github_controller.rb +91 -0
- data/app/controllers/aven/oauth/google_controller.rb +64 -0
- data/app/controllers/aven/workspaces_controller.rb +20 -0
- data/app/controllers/concerns/aven/authentication.rb +49 -0
- data/app/controllers/concerns/aven/controller_helpers.rb +38 -0
- data/app/helpers/aven/application_helper.rb +2 -6
- data/app/models/aven/app_record.rb +1 -1
- data/app/models/aven/app_record_schema.rb +0 -1
- data/app/models/aven/log.rb +0 -1
- data/app/models/aven/loggable.rb +2 -3
- data/app/models/aven/user.rb +0 -23
- data/app/models/aven/workspace.rb +49 -5
- data/app/models/aven/workspace_role.rb +0 -1
- data/app/models/aven/workspace_user.rb +0 -1
- data/app/models/aven/workspace_user_role.rb +0 -1
- data/config/routes.rb +22 -7
- data/db/migrate/{20251003090752_create_aven_users.rb → 20200101000001_create_aven_users.rb} +1 -1
- data/db/migrate/{20251004182010_create_aven_workspace_users.rb → 20200101000003_create_aven_workspace_users.rb} +1 -1
- data/db/migrate/{20251004182020_create_aven_workspace_roles.rb → 20200101000004_create_aven_workspace_roles.rb} +1 -1
- data/db/migrate/{20251004182030_create_aven_workspace_user_roles.rb → 20200101000005_create_aven_workspace_user_roles.rb} +1 -1
- data/db/migrate/{20251004190000_create_aven_logs.rb → 20200101000006_create_aven_logs.rb} +2 -3
- data/db/migrate/{20251004190100_create_aven_app_record_schemas.rb → 20200101000007_create_aven_app_record_schemas.rb} +0 -1
- data/db/migrate/{20251004190110_create_aven_app_records.rb → 20200101000008_create_aven_app_records.rb} +0 -1
- data/lib/aven/configuration.rb +26 -10
- data/lib/aven/engine.rb +15 -16
- data/lib/aven/model/tenant_model.rb +91 -0
- data/lib/aven/model.rb +6 -0
- data/lib/aven/version.rb +1 -1
- metadata +42 -69
- data/config/initializers/devise.rb +0 -43
- /data/db/migrate/{20251004182000_create_aven_workspaces.rb → 20200101000002_create_aven_workspaces.rb} +0 -0
- /data/lib/tasks/{sqema_tasks.rake → aven_tasks.rake} +0 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# GitHub OAuth Implementation Documentation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
GitHub OAuth 2.0 integration for the Aven application. This implementation supports authentication using GitHub accounts.
|
|
6
|
+
|
|
7
|
+
**Controller**: `app/controllers/aven/oauth/github_controller.rb`
|
|
8
|
+
**Routes**: `config/routes.rb:22-24`
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Default Scopes
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
user:email
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Scope Breakdown
|
|
19
|
+
|
|
20
|
+
| Scope | Purpose | Description |
|
|
21
|
+
|-------|---------|-------------|
|
|
22
|
+
| `user:email` | Read email addresses | Grants read-only access to user's email addresses (including private emails) |
|
|
23
|
+
|
|
24
|
+
**Note**: GitHub scopes don't require admin approval. Users consent individually.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## OAuth 2.0 Flow
|
|
29
|
+
|
|
30
|
+
### 1. Authorization Request
|
|
31
|
+
|
|
32
|
+
**Endpoint**: `https://github.com/login/oauth/authorize`
|
|
33
|
+
|
|
34
|
+
**Parameters**:
|
|
35
|
+
```ruby
|
|
36
|
+
{
|
|
37
|
+
client_id: "YOUR_CLIENT_ID",
|
|
38
|
+
redirect_uri: "https://yourdomain.com/oauth/github/callback",
|
|
39
|
+
scope: "user:email",
|
|
40
|
+
state: "RANDOM_STATE_TOKEN"
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 2. Token Exchange
|
|
45
|
+
|
|
46
|
+
**Endpoint**: `https://github.com/login/oauth/access_token`
|
|
47
|
+
|
|
48
|
+
**Parameters**:
|
|
49
|
+
```ruby
|
|
50
|
+
{
|
|
51
|
+
client_id: "YOUR_CLIENT_ID",
|
|
52
|
+
client_secret: "YOUR_CLIENT_SECRET",
|
|
53
|
+
code: "AUTHORIZATION_CODE",
|
|
54
|
+
redirect_uri: "https://yourdomain.com/oauth/github/callback"
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Headers**:
|
|
59
|
+
```ruby
|
|
60
|
+
{
|
|
61
|
+
"Accept" => "application/json"
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 3. User Info Retrieval
|
|
66
|
+
|
|
67
|
+
**User Profile Endpoint**: `https://api.github.com/user`
|
|
68
|
+
|
|
69
|
+
**Response**:
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"id": 12345678,
|
|
73
|
+
"login": "johndoe",
|
|
74
|
+
"name": "John Doe",
|
|
75
|
+
"email": "john@example.com",
|
|
76
|
+
"avatar_url": "https://avatars.githubusercontent.com/u/12345678",
|
|
77
|
+
"bio": "Developer",
|
|
78
|
+
"company": "Acme Inc"
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**User Emails Endpoint**: `https://api.github.com/user/emails`
|
|
83
|
+
|
|
84
|
+
Used when public email is not available.
|
|
85
|
+
|
|
86
|
+
**Response**:
|
|
87
|
+
```json
|
|
88
|
+
[
|
|
89
|
+
{
|
|
90
|
+
"email": "john@example.com",
|
|
91
|
+
"primary": true,
|
|
92
|
+
"verified": true,
|
|
93
|
+
"visibility": "private"
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Implementation Logic**:
|
|
99
|
+
- First tries to get email from user profile
|
|
100
|
+
- If email is null/blank, fetches from `/user/emails` endpoint
|
|
101
|
+
- Uses the primary verified email
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Configuration
|
|
106
|
+
|
|
107
|
+
### Application Configuration
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
config.oauth_providers = {
|
|
111
|
+
github: {
|
|
112
|
+
client_id: ENV['GITHUB_CLIENT_ID'],
|
|
113
|
+
client_secret: ENV['GITHUB_CLIENT_SECRET'],
|
|
114
|
+
# Optional configurations:
|
|
115
|
+
scope: "user:email read:user"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Environment Variables
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
GITHUB_CLIENT_ID=Iv1.a1b2c3d4e5f6g7h8
|
|
124
|
+
GITHUB_CLIENT_SECRET=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## GitHub OAuth App Setup
|
|
130
|
+
|
|
131
|
+
### 1. Create OAuth App
|
|
132
|
+
|
|
133
|
+
1. Go to [GitHub Settings](https://github.com/settings/developers)
|
|
134
|
+
2. Click **OAuth Apps** > **New OAuth App**
|
|
135
|
+
3. Fill in:
|
|
136
|
+
- **Application name**: Your application name
|
|
137
|
+
- **Homepage URL**: `https://yourdomain.com`
|
|
138
|
+
- **Authorization callback URL**: `https://yourdomain.com/oauth/github/callback`
|
|
139
|
+
- **Application description**: (optional)
|
|
140
|
+
4. Click **Register application**
|
|
141
|
+
5. Copy the **Client ID**
|
|
142
|
+
6. Click **Generate a new client secret**
|
|
143
|
+
7. Copy the **Client Secret** (shown only once)
|
|
144
|
+
|
|
145
|
+
### 2. Note Your Credentials
|
|
146
|
+
|
|
147
|
+
- **Client ID**: `Iv1.a1b2c3d4e5f6g7h8`
|
|
148
|
+
- **Client Secret**: `a1b2c3d4e5f6g7h8i9j0...`
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Routes
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
# GitHub OAuth
|
|
156
|
+
get "github", to: "github#create", as: :github
|
|
157
|
+
get "github/callback", to: "github#callback", as: :github_callback
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Available Routes**:
|
|
161
|
+
- `GET /oauth/github` → Initiates OAuth flow
|
|
162
|
+
- `GET /oauth/github/callback` → Handles OAuth callback
|
|
163
|
+
|
|
164
|
+
**Named Routes**:
|
|
165
|
+
- `oauth_github_path` → `/oauth/github`
|
|
166
|
+
- `oauth_github_callback_path` → `/oauth/github/callback`
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Additional Scopes (Optional)
|
|
171
|
+
|
|
172
|
+
### User Information
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
scope: "user:email read:user"
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
- `read:user` - Read all user profile data
|
|
179
|
+
- `user` - Full access to user profile (read and write)
|
|
180
|
+
|
|
181
|
+
### Repository Access
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
scope: "user:email repo"
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
- `repo` - Full control of private repositories
|
|
188
|
+
- `public_repo` - Access to public repositories only
|
|
189
|
+
|
|
190
|
+
### Organization Access
|
|
191
|
+
|
|
192
|
+
```ruby
|
|
193
|
+
scope: "user:email read:org"
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
- `read:org` - Read org and team membership
|
|
197
|
+
- `write:org` - Manage org and teams
|
|
198
|
+
|
|
199
|
+
### Gist Access
|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
scope: "user:email gist"
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
- `gist` - Create and edit gists
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## GitHub API Usage
|
|
210
|
+
|
|
211
|
+
After authentication, use the access token to call GitHub API v3:
|
|
212
|
+
|
|
213
|
+
### Get User Repositories
|
|
214
|
+
|
|
215
|
+
```ruby
|
|
216
|
+
GET https://api.github.com/user/repos
|
|
217
|
+
Authorization: Bearer {access_token}
|
|
218
|
+
Accept: application/vnd.github.v3+json
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Get User Organizations
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
GET https://api.github.com/user/orgs
|
|
225
|
+
Authorization: Bearer {access_token}
|
|
226
|
+
Accept: application/vnd.github.v3+json
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Get Authenticated User
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
GET https://api.github.com/user
|
|
233
|
+
Authorization: Bearer {access_token}
|
|
234
|
+
Accept: application/vnd.github.v3+json
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Official Documentation
|
|
240
|
+
|
|
241
|
+
1. **GitHub OAuth Documentation**
|
|
242
|
+
https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps
|
|
243
|
+
|
|
244
|
+
2. **GitHub API Documentation**
|
|
245
|
+
https://docs.github.com/en/rest
|
|
246
|
+
|
|
247
|
+
3. **GitHub OAuth Scopes**
|
|
248
|
+
https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps
|
|
249
|
+
|
|
250
|
+
4. **GitHub REST API - Users**
|
|
251
|
+
https://docs.github.com/en/rest/users/users
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Implementation Details
|
|
256
|
+
|
|
257
|
+
### Email Handling
|
|
258
|
+
|
|
259
|
+
The implementation has special logic for retrieving emails:
|
|
260
|
+
|
|
261
|
+
```ruby
|
|
262
|
+
# Fetch user profile
|
|
263
|
+
user_data = github_api_request(USER_INFO_URL, access_token)
|
|
264
|
+
|
|
265
|
+
# Fetch primary email if not public
|
|
266
|
+
email = user_data[:email]
|
|
267
|
+
if email.blank?
|
|
268
|
+
emails_data = github_api_request(USER_EMAIL_URL, access_token)
|
|
269
|
+
primary_email = emails_data.find { |e| e[:primary] && e[:verified] }
|
|
270
|
+
email = primary_email[:email] if primary_email
|
|
271
|
+
end
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
This ensures we always get an email, even if the user's GitHub email is private.
|
|
275
|
+
|
|
276
|
+
### API Request Headers
|
|
277
|
+
|
|
278
|
+
GitHub requires specific headers:
|
|
279
|
+
|
|
280
|
+
```ruby
|
|
281
|
+
request["Authorization"] = "Bearer #{access_token}"
|
|
282
|
+
request["Accept"] = "application/vnd.github.v3+json"
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Troubleshooting
|
|
288
|
+
|
|
289
|
+
### "redirect_uri_mismatch"
|
|
290
|
+
|
|
291
|
+
**Solution**: Ensure the callback URL in your GitHub OAuth App settings exactly matches:
|
|
292
|
+
```
|
|
293
|
+
https://yourdomain.com/oauth/github/callback
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### "bad_verification_code"
|
|
297
|
+
|
|
298
|
+
**Solution**: The authorization code has already been used or expired. Ask user to try again.
|
|
299
|
+
|
|
300
|
+
### No email returned
|
|
301
|
+
|
|
302
|
+
**Solution**: Ensure you're requesting the `user:email` scope. The implementation will fallback to the `/user/emails` endpoint.
|
|
303
|
+
|
|
304
|
+
### API rate limiting
|
|
305
|
+
|
|
306
|
+
**Solution**: Authenticated requests have a limit of 5,000 requests per hour. For unauthenticated requests, it's 60 per hour.
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Security Considerations
|
|
311
|
+
|
|
312
|
+
### Token Expiration
|
|
313
|
+
|
|
314
|
+
GitHub access tokens **do not expire** by default (unlike other OAuth providers). This means:
|
|
315
|
+
- Tokens remain valid until manually revoked
|
|
316
|
+
- No need for refresh token logic
|
|
317
|
+
- Important to handle token revocation properly
|
|
318
|
+
|
|
319
|
+
Users can revoke access at: https://github.com/settings/applications
|
|
320
|
+
|
|
321
|
+
### Scope Permissions
|
|
322
|
+
|
|
323
|
+
Request only the minimum scopes needed:
|
|
324
|
+
- ✅ `user:email` - For authentication only
|
|
325
|
+
- ❌ `repo` - Don't request unless you need repository access
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
**Last Updated**: October 15, 2025
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# Google OAuth Implementation Documentation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Google OAuth 2.0 integration for the Aven application. This implementation supports authentication using Google accounts (both personal Gmail accounts and Google Workspace accounts).
|
|
6
|
+
|
|
7
|
+
**Controller**: `app/controllers/aven/oauth/google_controller.rb`
|
|
8
|
+
**Routes**: `config/routes.rb:18-20`
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Default Scopes
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
openid email profile
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Scope Breakdown
|
|
19
|
+
|
|
20
|
+
| Scope | Purpose | Admin Consent Required |
|
|
21
|
+
|-------|---------|------------------------|
|
|
22
|
+
| `openid` | OpenID Connect authentication | No |
|
|
23
|
+
| `email` | Access user's email address | No |
|
|
24
|
+
| `profile` | Access user's basic profile (name, picture) | No |
|
|
25
|
+
|
|
26
|
+
**Note**: These are basic OAuth scopes that don't require admin approval. Users consent individually.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## OAuth 2.0 Flow
|
|
31
|
+
|
|
32
|
+
### 1. Authorization Request
|
|
33
|
+
|
|
34
|
+
**Endpoint**: `https://accounts.google.com/o/oauth2/v2/auth`
|
|
35
|
+
|
|
36
|
+
**Parameters**:
|
|
37
|
+
```ruby
|
|
38
|
+
{
|
|
39
|
+
client_id: "YOUR_CLIENT_ID",
|
|
40
|
+
redirect_uri: "https://yourdomain.com/oauth/google/callback",
|
|
41
|
+
response_type: "code",
|
|
42
|
+
scope: "openid email profile",
|
|
43
|
+
state: "RANDOM_STATE_TOKEN",
|
|
44
|
+
access_type: "offline", # Optional: for refresh tokens
|
|
45
|
+
prompt: "select_account" # Optional: force account selection
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 2. Token Exchange
|
|
50
|
+
|
|
51
|
+
**Endpoint**: `https://www.googleapis.com/oauth2/v4/token`
|
|
52
|
+
|
|
53
|
+
**Parameters**:
|
|
54
|
+
```ruby
|
|
55
|
+
{
|
|
56
|
+
code: "AUTHORIZATION_CODE",
|
|
57
|
+
client_id: "YOUR_CLIENT_ID",
|
|
58
|
+
client_secret: "YOUR_CLIENT_SECRET",
|
|
59
|
+
redirect_uri: "https://yourdomain.com/oauth/google/callback",
|
|
60
|
+
grant_type: "authorization_code"
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 3. User Info Retrieval
|
|
65
|
+
|
|
66
|
+
**Endpoint**: `https://www.googleapis.com/oauth2/v3/userinfo`
|
|
67
|
+
|
|
68
|
+
**Response**:
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"sub": "1234567890",
|
|
72
|
+
"email": "user@gmail.com",
|
|
73
|
+
"email_verified": true,
|
|
74
|
+
"name": "John Doe",
|
|
75
|
+
"picture": "https://lh3.googleusercontent.com/a/..."
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Configuration
|
|
82
|
+
|
|
83
|
+
### Application Configuration
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
config.oauth_providers = {
|
|
87
|
+
google: {
|
|
88
|
+
client_id: ENV['GOOGLE_CLIENT_ID'],
|
|
89
|
+
client_secret: ENV['GOOGLE_CLIENT_SECRET'],
|
|
90
|
+
# Optional configurations:
|
|
91
|
+
scope: "openid email profile",
|
|
92
|
+
access_type: "offline", # Receive refresh tokens
|
|
93
|
+
prompt: "select_account" # Force account selection
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Environment Variables
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
GOOGLE_CLIENT_ID=123456789-abcdefghijklmnop.apps.googleusercontent.com
|
|
102
|
+
GOOGLE_CLIENT_SECRET=GOCSPX-your_client_secret
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Google Cloud Console Setup
|
|
108
|
+
|
|
109
|
+
### 1. Create OAuth Client ID
|
|
110
|
+
|
|
111
|
+
1. Go to [Google Cloud Console](https://console.cloud.google.com)
|
|
112
|
+
2. Select or create a project
|
|
113
|
+
3. Navigate to **APIs & Services** > **Credentials**
|
|
114
|
+
4. Click **Create Credentials** > **OAuth client ID**
|
|
115
|
+
5. Select **Web application**
|
|
116
|
+
6. Configure:
|
|
117
|
+
- **Name**: Your application name
|
|
118
|
+
- **Authorized JavaScript origins**: `https://yourdomain.com`
|
|
119
|
+
- **Authorized redirect URIs**: `https://yourdomain.com/oauth/google/callback`
|
|
120
|
+
7. Click **Create**
|
|
121
|
+
8. Copy the **Client ID** and **Client Secret**
|
|
122
|
+
|
|
123
|
+
### 2. Configure OAuth Consent Screen
|
|
124
|
+
|
|
125
|
+
1. Go to **APIs & Services** > **OAuth consent screen**
|
|
126
|
+
2. Select **External** (for public apps) or **Internal** (for Google Workspace only)
|
|
127
|
+
3. Fill in:
|
|
128
|
+
- **App name**: Your application name
|
|
129
|
+
- **User support email**: Your email
|
|
130
|
+
- **Developer contact information**: Your email
|
|
131
|
+
4. Add scopes (optional for basic profile)
|
|
132
|
+
5. Save and continue
|
|
133
|
+
|
|
134
|
+
### 3. Note Your Credentials
|
|
135
|
+
|
|
136
|
+
- **Client ID**: `123456789-...apps.googleusercontent.com`
|
|
137
|
+
- **Client Secret**: `GOCSPX-...`
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Routes
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
# Google OAuth
|
|
145
|
+
get "google", to: "google#create", as: :google
|
|
146
|
+
get "google/callback", to: "google#callback", as: :google_callback
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Available Routes**:
|
|
150
|
+
- `GET /oauth/google` → Initiates OAuth flow
|
|
151
|
+
- `GET /oauth/google/callback` → Handles OAuth callback
|
|
152
|
+
|
|
153
|
+
**Named Routes**:
|
|
154
|
+
- `oauth_google_path` → `/oauth/google`
|
|
155
|
+
- `oauth_google_callback_path` → `/oauth/google/callback`
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Additional Scopes (Optional)
|
|
160
|
+
|
|
161
|
+
If you need access to more Google services, add these scopes:
|
|
162
|
+
|
|
163
|
+
### Gmail API
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
scope: "openid email profile https://www.googleapis.com/auth/gmail.readonly https://www.googleapis.com/auth/gmail.send"
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
- `gmail.readonly` - Read emails
|
|
170
|
+
- `gmail.send` - Send emails
|
|
171
|
+
- `gmail.modify` - Read and modify emails
|
|
172
|
+
|
|
173
|
+
### Google Calendar
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
scope: "openid email profile https://www.googleapis.com/auth/calendar.readonly"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
- `calendar.readonly` - Read calendar events
|
|
180
|
+
- `calendar` - Full calendar access
|
|
181
|
+
|
|
182
|
+
### Google Drive
|
|
183
|
+
|
|
184
|
+
```ruby
|
|
185
|
+
scope: "openid email profile https://www.googleapis.com/auth/drive.readonly"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
- `drive.readonly` - Read Drive files
|
|
189
|
+
- `drive.file` - Access files created by app
|
|
190
|
+
- `drive` - Full Drive access
|
|
191
|
+
|
|
192
|
+
**Important**: Extended scopes may require verification by Google if your app is public.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Official Documentation
|
|
197
|
+
|
|
198
|
+
1. **Google OAuth 2.0 Guide**
|
|
199
|
+
https://developers.google.com/identity/protocols/oauth2
|
|
200
|
+
|
|
201
|
+
2. **Using OAuth 2.0 to Access Google APIs**
|
|
202
|
+
https://developers.google.com/identity/protocols/oauth2/web-server
|
|
203
|
+
|
|
204
|
+
3. **Google API Scopes**
|
|
205
|
+
https://developers.google.com/identity/protocols/oauth2/scopes
|
|
206
|
+
|
|
207
|
+
4. **OpenID Connect**
|
|
208
|
+
https://developers.google.com/identity/openid-connect/openid-connect
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Security Considerations
|
|
213
|
+
|
|
214
|
+
### Refresh Tokens
|
|
215
|
+
|
|
216
|
+
Set `access_type: "offline"` to receive refresh tokens for long-term access:
|
|
217
|
+
|
|
218
|
+
```ruby
|
|
219
|
+
access_type: oauth_config[:access_type] || "offline"
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Account Selection
|
|
223
|
+
|
|
224
|
+
Use `prompt: "select_account"` to force users to select which Google account to use:
|
|
225
|
+
|
|
226
|
+
```ruby
|
|
227
|
+
prompt: oauth_config[:prompt] || "select_account"
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
This is useful if users have multiple Google accounts.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Troubleshooting
|
|
235
|
+
|
|
236
|
+
### "redirect_uri_mismatch"
|
|
237
|
+
|
|
238
|
+
**Solution**: Ensure the redirect URI in Google Cloud Console exactly matches:
|
|
239
|
+
```
|
|
240
|
+
https://yourdomain.com/oauth/google/callback
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### "invalid_client"
|
|
244
|
+
|
|
245
|
+
**Solution**: Check that your Client ID and Client Secret are correct.
|
|
246
|
+
|
|
247
|
+
### "access_denied"
|
|
248
|
+
|
|
249
|
+
**Solution**: User declined the consent screen. This is normal user behavior.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
**Last Updated**: October 15, 2025
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module Aven
|
|
7
|
+
module Oauth
|
|
8
|
+
class EntraIdController < BaseController
|
|
9
|
+
# Microsoft Entra ID (formerly Azure AD) OAuth endpoints
|
|
10
|
+
# Supports both single-tenant and multi-tenant configurations
|
|
11
|
+
#
|
|
12
|
+
# MINIMAL_SCOPE: Authentication only (no Graph API access)
|
|
13
|
+
# Use this for simple login without accessing user data beyond basic profile
|
|
14
|
+
MINIMAL_SCOPE = "openid email profile"
|
|
15
|
+
|
|
16
|
+
# DEFAULT_SCOPE: Includes Graph API access for contacts and email
|
|
17
|
+
DEFAULT_SCOPE = "openid email profile User.Read Contacts.Read Mail.Send Mail.Read"
|
|
18
|
+
|
|
19
|
+
protected
|
|
20
|
+
|
|
21
|
+
def authorization_url(state)
|
|
22
|
+
params = {
|
|
23
|
+
client_id: oauth_config[:client_id],
|
|
24
|
+
redirect_uri: callback_url,
|
|
25
|
+
response_type: "code",
|
|
26
|
+
scope: oauth_config[:scope] || DEFAULT_SCOPE,
|
|
27
|
+
state:,
|
|
28
|
+
response_mode: "query"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# Optionally add domain_hint for faster login
|
|
32
|
+
params[:domain_hint] = oauth_config[:domain_hint] if oauth_config[:domain_hint].present?
|
|
33
|
+
|
|
34
|
+
# Optionally add prompt parameter
|
|
35
|
+
params[:prompt] = oauth_config[:prompt] if oauth_config[:prompt].present?
|
|
36
|
+
|
|
37
|
+
"#{entra_authorization_url}?#{params.to_query}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def exchange_code_for_token(code)
|
|
41
|
+
params = {
|
|
42
|
+
client_id: oauth_config[:client_id],
|
|
43
|
+
client_secret: oauth_config[:client_secret],
|
|
44
|
+
code:,
|
|
45
|
+
redirect_uri: callback_url,
|
|
46
|
+
grant_type: "authorization_code",
|
|
47
|
+
scope: oauth_config[:scope] || DEFAULT_SCOPE
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
oauth_request(URI(entra_token_url), params)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def fetch_user_info(access_token)
|
|
54
|
+
response = oauth_get_request(URI(entra_userinfo_url), access_token)
|
|
55
|
+
|
|
56
|
+
{
|
|
57
|
+
id: response[:id] || response[:sub],
|
|
58
|
+
email: response[:mail] || response[:userPrincipalName] || response[:email],
|
|
59
|
+
name: response[:displayName] || response[:name],
|
|
60
|
+
picture: nil # Microsoft Graph doesn't return picture in userinfo by default
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def callback_url
|
|
67
|
+
aven.oauth_entra_id_callback_url(host: request.host, protocol: "https://")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def oauth_config
|
|
71
|
+
@oauth_config ||= Aven.configuration.oauth_providers[:entra_id] || raise("Microsoft Entra ID OAuth not configured")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def tenant_id
|
|
75
|
+
@tenant_id ||= oauth_config[:tenant_id] || "common"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def entra_authorization_url
|
|
79
|
+
"https://login.microsoftonline.com/#{tenant_id}/oauth2/v2.0/authorize"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def entra_token_url
|
|
83
|
+
"https://login.microsoftonline.com/#{tenant_id}/oauth2/v2.0/token"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def entra_userinfo_url
|
|
87
|
+
# Using Microsoft Graph API for user info
|
|
88
|
+
"https://graph.microsoft.com/v1.0/me"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|