mirror of
				https://github.com/arcan1s/ahriman.git
				synced 2025-11-03 23:33:41 +00:00 
			
		
		
		
	feat: read username if email is not available for oauth provider
Also add recipe for OAuth with GitHub setup
This commit is contained in:
		@ -10,6 +10,7 @@ Collection of the examples of docker compose configuration files, which covers s
 | 
			
		||||
* [Distributed manual](distributed-manual): same as [distributed](distributed), but two nodes and update process must be run on worker node manually.
 | 
			
		||||
* [i686](i686): non-x86_64 architecture setup.
 | 
			
		||||
* [Multi repo](multirepo): run web service with two separated repositories.
 | 
			
		||||
* [OAuth](oauth): web service with OAuth (GitHub provider) authentication enabled.
 | 
			
		||||
* [Pull](pull): normal service, but in addition with pulling packages from another source (e.g. GitHub repository).
 | 
			
		||||
* [Sign](sign): create repository with database signing.
 | 
			
		||||
* [Web](web): simple web service with authentication enabled.
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										15
									
								
								recipes/oauth/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								recipes/oauth/README.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
			
		||||
# OAuth
 | 
			
		||||
 | 
			
		||||
1. Create user from `AHRIMAN_OAUTH_USER` environment variable (same as GitHub user).
 | 
			
		||||
2. Configure OAuth to use GitHub provider with client ID and secret specified in variables `AHRIMAN_OAUTH_CLIENT_ID` and `AHRIMAN_OAUTH_CLIENT_SECRET` variables respectively.   
 | 
			
		||||
3. Setup repository named `ahriman-demo` with architecture `x86_64`.
 | 
			
		||||
4. Start web server at port `8080`.
 | 
			
		||||
5. Repository is available at `http://localhost:8080/repo`.
 | 
			
		||||
 | 
			
		||||
Before you start, you need to create an application. It can be done by:
 | 
			
		||||
 | 
			
		||||
1. Go to `https://github.com/settings/applications/new`
 | 
			
		||||
2. Set application name and its homepage.
 | 
			
		||||
3. Set callback url to `http://localhost:8080/api/v1/login`
 | 
			
		||||
4. Copy Client ID.
 | 
			
		||||
5. Generate new client secret and copy it.
 | 
			
		||||
							
								
								
									
										58
									
								
								recipes/oauth/compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								recipes/oauth/compose.yml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,58 @@
 | 
			
		||||
services:
 | 
			
		||||
  backend:
 | 
			
		||||
    image: arcan1s/ahriman:edge
 | 
			
		||||
    privileged: true
 | 
			
		||||
 | 
			
		||||
    environment:
 | 
			
		||||
      AHRIMAN_DEBUG: yes
 | 
			
		||||
      AHRIMAN_OAUTH_CLIENT_ID: ${AHRIMAN_OAUTH_CLIENT_ID}
 | 
			
		||||
      AHRIMAN_OAUTH_CLIENT_SECRET: ${AHRIMAN_OAUTH_CLIENT_SECRET}
 | 
			
		||||
      AHRIMAN_OUTPUT: console
 | 
			
		||||
      AHRIMAN_PORT: 8080
 | 
			
		||||
      AHRIMAN_PRESETUP_COMMAND: sudo -u ahriman ahriman user-add ${AHRIMAN_OAUTH_USER} -R full -p ""
 | 
			
		||||
      AHRIMAN_REPOSITORY: ahriman-demo
 | 
			
		||||
      AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
 | 
			
		||||
 | 
			
		||||
    configs:
 | 
			
		||||
      - source: service
 | 
			
		||||
        target: /etc/ahriman.ini.d/99-settings.ini
 | 
			
		||||
 | 
			
		||||
    volumes:
 | 
			
		||||
      - type: volume
 | 
			
		||||
        source: repository
 | 
			
		||||
        target: /var/lib/ahriman
 | 
			
		||||
        volume:
 | 
			
		||||
          nocopy: true
 | 
			
		||||
 | 
			
		||||
    healthcheck:
 | 
			
		||||
      test: curl --fail --silent --output /dev/null http://backend:8080/api/v1/info
 | 
			
		||||
      interval: 10s
 | 
			
		||||
      start_period: 30s
 | 
			
		||||
 | 
			
		||||
    command: web
 | 
			
		||||
 | 
			
		||||
  frontend:
 | 
			
		||||
    image: nginx
 | 
			
		||||
    ports:
 | 
			
		||||
      - 8080:80
 | 
			
		||||
 | 
			
		||||
    configs:
 | 
			
		||||
      - source: nginx
 | 
			
		||||
        target: /etc/nginx/conf.d/default.conf
 | 
			
		||||
 | 
			
		||||
    volumes:
 | 
			
		||||
      - type: volume
 | 
			
		||||
        source: repository
 | 
			
		||||
        target: /srv
 | 
			
		||||
        read_only: true
 | 
			
		||||
        volume:
 | 
			
		||||
          nocopy: true
 | 
			
		||||
 | 
			
		||||
configs:
 | 
			
		||||
  nginx:
 | 
			
		||||
    file: nginx.conf
 | 
			
		||||
  service:
 | 
			
		||||
    file: service.ini
 | 
			
		||||
 | 
			
		||||
volumes:
 | 
			
		||||
  repository:
 | 
			
		||||
							
								
								
									
										18
									
								
								recipes/oauth/nginx.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								recipes/oauth/nginx.conf
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
server {
 | 
			
		||||
    listen 80;
 | 
			
		||||
 | 
			
		||||
    location /repo {
 | 
			
		||||
        rewrite ^/repo/(.*) /$1 break;
 | 
			
		||||
        autoindex on;
 | 
			
		||||
        root /srv/ahriman/repository;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    location / {
 | 
			
		||||
        proxy_set_header Host $host;
 | 
			
		||||
        proxy_set_header X-Real-IP $remote_addr;
 | 
			
		||||
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 | 
			
		||||
        proxy_set_header X-Forwarder-Proto $scheme;
 | 
			
		||||
 | 
			
		||||
        proxy_pass http://backend:8080;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								recipes/oauth/service.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								recipes/oauth/service.ini
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
			
		||||
[auth]
 | 
			
		||||
target = oauth
 | 
			
		||||
client_id = $AHRIMAN_OAUTH_CLIENT_ID
 | 
			
		||||
client_secret = $AHRIMAN_OAUTH_CLIENT_SECRET
 | 
			
		||||
 | 
			
		||||
oauth_icon = github
 | 
			
		||||
oauth_provider = GithubClient
 | 
			
		||||
oauth_scopes = read:user
 | 
			
		||||
 | 
			
		||||
[web]
 | 
			
		||||
address = http://localhost:8080
 | 
			
		||||
@ -130,7 +130,7 @@ class OAuth(Mapping):
 | 
			
		||||
            client.access_token = access_token
 | 
			
		||||
 | 
			
		||||
            user, _ = await client.user_info()
 | 
			
		||||
            username: str = user.email  # type: ignore[attr-defined]
 | 
			
		||||
            username: str = user.email or user.username  # type: ignore[attr-defined]
 | 
			
		||||
            return username
 | 
			
		||||
        except Exception:
 | 
			
		||||
            self.logger.exception("got exception while performing request")
 | 
			
		||||
 | 
			
		||||
@ -75,6 +75,17 @@ async def test_get_oauth_username(oauth: OAuth, mocker: MockerFixture) -> None:
 | 
			
		||||
    assert email == "email"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def test_get_oauth_username_empty_email(oauth: OAuth, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must read username if email is not available
 | 
			
		||||
    """
 | 
			
		||||
    mocker.patch("aioauth_client.GoogleClient.get_access_token", return_value=("token", ""))
 | 
			
		||||
    mocker.patch("aioauth_client.GoogleClient.user_info", return_value=(aioauth_client.User(username="username"), ""))
 | 
			
		||||
 | 
			
		||||
    username = await oauth.get_oauth_username("code")
 | 
			
		||||
    assert username == "username"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def test_get_oauth_username_exception_1(oauth: OAuth, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must return None in case of OAuth request error (get_access_token)
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user