mirror of
https://github.com/byo-software/steam-openid-connect-provider.git
synced 2025-01-08 09:36:21 +00:00
Merge branch 'master' of https://github.com/neothor/steam-openid-connect-provider
This commit is contained in:
commit
93bfbb290e
37 changed files with 2702 additions and 216 deletions
275
.editorconfig
Normal file
275
.editorconfig
Normal file
|
@ -0,0 +1,275 @@
|
|||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Don't use tabs for indentation.
|
||||
[*]
|
||||
indent_style = space
|
||||
# (Please don't specify an indent_size here; that has too many unintended consequences.)
|
||||
|
||||
# Code files
|
||||
[*.{cs,csx,vb,vbx}]
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
charset = utf-8-bom
|
||||
|
||||
# XML project files
|
||||
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
|
||||
indent_size = 2
|
||||
|
||||
# XML config files
|
||||
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
|
||||
indent_size = 2
|
||||
|
||||
# JSON files
|
||||
[*.json]
|
||||
indent_size = 2
|
||||
|
||||
# Powershell files
|
||||
[*.ps1]
|
||||
indent_size = 2
|
||||
|
||||
# Shell script files
|
||||
[*.sh]
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
|
||||
# Dotnet code style settings:
|
||||
[*.{cs,vb}]
|
||||
|
||||
# IDE0055: Fix formatting
|
||||
dotnet_diagnostic.IDE0055.severity = warning
|
||||
|
||||
# Sort using and Import directives with System.* appearing first
|
||||
dotnet_sort_system_directives_first = true
|
||||
dotnet_separate_import_directive_groups = false
|
||||
# Avoid "this." and "Me." if not necessary
|
||||
dotnet_style_qualification_for_field = false:refactoring
|
||||
dotnet_style_qualification_for_property = false:refactoring
|
||||
dotnet_style_qualification_for_method = false:refactoring
|
||||
dotnet_style_qualification_for_event = false:refactoring
|
||||
|
||||
# Use language keywords instead of framework type names for type references
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
|
||||
# Suggest more modern language features when available
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
|
||||
# Non-private static fields are PascalCase
|
||||
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields
|
||||
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
|
||||
|
||||
dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
|
||||
|
||||
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
|
||||
|
||||
# Non-private readonly fields are PascalCase
|
||||
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields
|
||||
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = non_private_readonly_field_style
|
||||
|
||||
dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly
|
||||
|
||||
dotnet_naming_style.non_private_readonly_field_style.capitalization = pascal_case
|
||||
|
||||
# Constants are PascalCase
|
||||
dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants
|
||||
dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style
|
||||
|
||||
dotnet_naming_symbols.constants.applicable_kinds = field, local
|
||||
dotnet_naming_symbols.constants.required_modifiers = const
|
||||
|
||||
dotnet_naming_style.constant_style.capitalization = pascal_case
|
||||
|
||||
# Static fields are camelCase and start with s_
|
||||
dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion
|
||||
dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields
|
||||
dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style
|
||||
|
||||
dotnet_naming_symbols.static_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.static_fields.required_modifiers = static
|
||||
|
||||
dotnet_naming_style.static_field_style.capitalization = camel_case
|
||||
dotnet_naming_style.static_field_style.required_prefix = s_
|
||||
|
||||
# Instance fields are camelCase and start with _
|
||||
dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion
|
||||
dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
|
||||
dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
|
||||
|
||||
dotnet_naming_symbols.instance_fields.applicable_kinds = field
|
||||
|
||||
dotnet_naming_style.instance_field_style.capitalization = camel_case
|
||||
dotnet_naming_style.instance_field_style.required_prefix = _
|
||||
|
||||
# Locals and parameters are camelCase
|
||||
dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion
|
||||
dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters
|
||||
dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style
|
||||
|
||||
dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local
|
||||
|
||||
dotnet_naming_style.camel_case_style.capitalization = camel_case
|
||||
|
||||
# Local functions are PascalCase
|
||||
dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
|
||||
dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style
|
||||
|
||||
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
|
||||
|
||||
dotnet_naming_style.local_function_style.capitalization = pascal_case
|
||||
|
||||
# By default, name items with PascalCase
|
||||
dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members
|
||||
dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style
|
||||
|
||||
dotnet_naming_symbols.all_members.applicable_kinds = *
|
||||
|
||||
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
||||
|
||||
# error RS2008: Enable analyzer release tracking for the analyzer project containing rule '{0}'
|
||||
dotnet_diagnostic.RS2008.severity = none
|
||||
|
||||
# IDE0035: Remove unreachable code
|
||||
dotnet_diagnostic.IDE0035.severity = warning
|
||||
|
||||
# IDE0036: Order modifiers
|
||||
dotnet_diagnostic.IDE0036.severity = warning
|
||||
|
||||
# IDE0043: Format string contains invalid placeholder
|
||||
dotnet_diagnostic.IDE0043.severity = warning
|
||||
|
||||
# IDE0044: Make field readonly
|
||||
dotnet_diagnostic.IDE0044.severity = warning
|
||||
|
||||
# RS0016: Only enable if API files are present
|
||||
dotnet_public_api_analyzer.require_api_files = true
|
||||
|
||||
# CSharp code style settings:
|
||||
[*.cs]
|
||||
# Newline settings
|
||||
csharp_new_line_before_open_brace = all
|
||||
csharp_new_line_before_else = true
|
||||
csharp_new_line_before_catch = true
|
||||
csharp_new_line_before_finally = true
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
csharp_new_line_between_query_expression_clauses = true
|
||||
|
||||
# Indentation preferences
|
||||
csharp_indent_block_contents = true
|
||||
csharp_indent_braces = false
|
||||
csharp_indent_case_contents = true
|
||||
csharp_indent_case_contents_when_block = true
|
||||
csharp_indent_switch_labels = true
|
||||
csharp_indent_labels = flush_left
|
||||
|
||||
# Prefer "var" everywhere
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
|
||||
# Prefer method-like constructs to have a block body
|
||||
csharp_style_expression_bodied_methods = false:none
|
||||
csharp_style_expression_bodied_constructors = false:none
|
||||
csharp_style_expression_bodied_operators = false:none
|
||||
|
||||
# Prefer property-like constructs to have an expression-body
|
||||
csharp_style_expression_bodied_properties = true:none
|
||||
csharp_style_expression_bodied_indexers = true:none
|
||||
csharp_style_expression_bodied_accessors = true:none
|
||||
|
||||
# Suggest more modern language features when available
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
csharp_style_throw_expression = true:suggestion
|
||||
csharp_style_conditional_delegate_call = true:suggestion
|
||||
|
||||
# Space preferences
|
||||
csharp_space_after_cast = false
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
csharp_space_after_comma = true
|
||||
csharp_space_after_dot = false
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
csharp_space_after_semicolon_in_for_statement = true
|
||||
csharp_space_around_binary_operators = before_and_after
|
||||
csharp_space_around_declaration_statements = do_not_ignore
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
csharp_space_before_comma = false
|
||||
csharp_space_before_dot = false
|
||||
csharp_space_before_open_square_brackets = false
|
||||
csharp_space_before_semicolon_in_for_statement = false
|
||||
csharp_space_between_empty_square_brackets = false
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
csharp_space_between_parentheses = false
|
||||
csharp_space_between_square_brackets = false
|
||||
|
||||
# Blocks are allowed
|
||||
csharp_prefer_braces = true:silent
|
||||
csharp_preserve_single_line_blocks = true
|
||||
csharp_preserve_single_line_statements = true
|
||||
|
||||
[src/CodeStyle/**.{cs,vb}]
|
||||
# warning RS0005: Do not use generic CodeAction.Create to create CodeAction
|
||||
dotnet_diagnostic.RS0005.severity = none
|
||||
|
||||
[src/{Analyzers,CodeStyle,Features,Workspaces,EditorFeatures,VisualStudio}/**/*.{cs,vb}]
|
||||
|
||||
# IDE0011: Add braces
|
||||
csharp_prefer_braces = when_multiline:warning
|
||||
# NOTE: We need the below severity entry for Add Braces due to https://github.com/dotnet/roslyn/issues/44201
|
||||
dotnet_diagnostic.IDE0011.severity = warning
|
||||
|
||||
# IDE0040: Add accessibility modifiers
|
||||
dotnet_diagnostic.IDE0040.severity = warning
|
||||
|
||||
# CONSIDER: Are IDE0051 and IDE0052 too noisy to be warnings for IDE editing scenarios? Should they be made build-only warnings?
|
||||
# IDE0051: Remove unused private member
|
||||
dotnet_diagnostic.IDE0051.severity = warning
|
||||
|
||||
# IDE0052: Remove unread private member
|
||||
dotnet_diagnostic.IDE0052.severity = warning
|
||||
|
||||
# IDE0059: Unnecessary assignment to a value
|
||||
dotnet_diagnostic.IDE0059.severity = warning
|
||||
|
||||
# IDE0060: Remove unused parameter
|
||||
dotnet_diagnostic.IDE0060.severity = warning
|
||||
|
||||
# CA1012: Abstract types should not have public constructors
|
||||
dotnet_diagnostic.CA1012.severity = warning
|
||||
|
||||
# CA1822: Make member static
|
||||
dotnet_diagnostic.CA1822.severity = warning
|
||||
|
||||
# Prefer "var" everywhere
|
||||
dotnet_diagnostic.IDE0007.severity = warning
|
||||
csharp_style_var_for_built_in_types = true:warning
|
||||
csharp_style_var_when_type_is_apparent = true:warning
|
||||
csharp_style_var_elsewhere = true:warning
|
||||
|
||||
[src/{VisualStudio}/**/*.{cs,vb}]
|
||||
# CA1822: Make member static
|
||||
# Not enforced as a build 'warning' for 'VisualStudio' layer due to large number of false positives from https://github.com/dotnet/roslyn-analyzers/issues/3857 and https://github.com/dotnet/roslyn-analyzers/issues/3858
|
||||
# Additionally, there is a risk of accidentally breaking an internal API that partners rely on though IVT.
|
||||
dotnet_diagnostic.CA1822.severity = suggestion
|
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -2,6 +2,7 @@
|
|||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
*.sh text eol=lf
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
|
|
53
.github/workflows/docker-image.yml
vendored
Normal file
53
.github/workflows/docker-image.yml
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
name: Docker Image CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
-
|
||||
name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/neothor/steam-openid-connect-provider
|
||||
tags: |
|
||||
type=schedule
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=sha
|
||||
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: src/
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -1,3 +1,7 @@
|
|||
|
||||
## For secrets in through docker env variables
|
||||
docker-compose.secrets.yml
|
||||
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
|
@ -330,3 +334,5 @@ ASALocalRun/
|
|||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
dist/
|
17
Dockerfile
17
Dockerfile
|
@ -1,17 +0,0 @@
|
|||
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
|
||||
WORKDIR /src
|
||||
COPY ["src/SteamOpenIdConnectProvider.csproj", "SteamOpenIdConnectProvider/"]
|
||||
RUN dotnet restore "SteamOpenIdConnectProvider/SteamOpenIdConnectProvider.csproj"
|
||||
COPY ["src/", "SteamOpenIdConnectProvider/"]
|
||||
WORKDIR "/src/SteamOpenIdConnectProvider"
|
||||
RUN dotnet build "SteamOpenIdConnectProvider.csproj" -c Release -o /app
|
||||
|
||||
FROM build AS publish
|
||||
RUN dotnet publish "SteamOpenIdConnectProvider.csproj" -c Release -o /app
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS base
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app .
|
||||
EXPOSE 80
|
||||
HEALTHCHECK CMD curl --fail http://localhost/health || exit 1
|
||||
ENTRYPOINT ["dotnet", "SteamOpenIdConnectProvider.dll"]
|
29
dev.local.plantuml
Normal file
29
dev.local.plantuml
Normal file
|
@ -0,0 +1,29 @@
|
|||
@startuml
|
||||
|
||||
node Host {
|
||||
component Browser
|
||||
|
||||
node Docker {
|
||||
component Proxy
|
||||
component Keycloak
|
||||
component SteamIdp
|
||||
component Postgresql
|
||||
|
||||
Proxy --> Keycloak: keycloak (http)
|
||||
Proxy --> SteamIdp: steamidp (http)
|
||||
Keycloak --> Postgresql: postgres
|
||||
|
||||
Proxy <-- Keycloak: dev.local (https)
|
||||
}
|
||||
|
||||
component SteamIdpDev as "SteamIdp"
|
||||
}
|
||||
|
||||
cloud Internet {
|
||||
component Steam
|
||||
}
|
||||
|
||||
Browser --> Proxy: dev.local (https)
|
||||
Proxy ..> SteamIdpDev: host.docker.internal (http)
|
||||
|
||||
@enduml
|
19
develop/keycloak/Dockerfile
Normal file
19
develop/keycloak/Dockerfile
Normal file
|
@ -0,0 +1,19 @@
|
|||
FROM jboss/keycloak
|
||||
|
||||
USER root
|
||||
|
||||
# Install sudo and utils to configure jboss user
|
||||
RUN microdnf update -y && \
|
||||
microdnf install -y sudo shadow-utils passwd && \
|
||||
microdnf clean all
|
||||
|
||||
# 'Fix' jboss user, add to sudoers
|
||||
RUN usermod --password jboss jboss && \
|
||||
usermod -aG wheel jboss && \
|
||||
sed -i 's/# includedir/includedir/' /etc/sudoers && \
|
||||
echo "jboss ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers.d/jboss
|
||||
|
||||
ADD ./add-to-truststore.sh /opt/jboss/startup-scripts/add-to-truststore.sh
|
||||
RUN chmod +x /opt/jboss/startup-scripts/add-to-truststore.sh
|
||||
|
||||
USER jboss
|
10
develop/keycloak/add-to-truststore.sh
Normal file
10
develop/keycloak/add-to-truststore.sh
Normal file
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
CACERTS=$(readlink -e $(dirname $(readlink -e $(which keytool)))"/../lib/security/cacerts")
|
||||
|
||||
sudo keytool \
|
||||
-import -trustcacerts \
|
||||
-alias "dev.local.crt" -file /tmp/dev.local.crt \
|
||||
-keystore ${CACERTS} \
|
||||
-storepass changeit \
|
||||
-noprompt
|
1787
develop/keycloak/dev-realm.json
Normal file
1787
develop/keycloak/dev-realm.json
Normal file
File diff suppressed because it is too large
Load diff
9
develop/proxy/Dockerfile
Normal file
9
develop/proxy/Dockerfile
Normal file
|
@ -0,0 +1,9 @@
|
|||
FROM nginx
|
||||
|
||||
ENV SSL_CERT=/tmp/dev.local.crt
|
||||
ENV SSL_KEY=/tmp/dev.local.key
|
||||
|
||||
ENV KEYCLOAK_URI=http://keycloak:8080
|
||||
ENV STEAMIDP_URI=http://steamidp:80
|
||||
|
||||
ADD ./proxy_ssl.conf.template /etc/nginx/templates/
|
21
develop/proxy/proxy_ssl.conf.template
Normal file
21
develop/proxy/proxy_ssl.conf.template
Normal file
|
@ -0,0 +1,21 @@
|
|||
server {
|
||||
listen 443 ssl;
|
||||
ssl_certificate ${SSL_CERT};
|
||||
ssl_certificate_key ${SSL_KEY};
|
||||
|
||||
proxy_set_header X-Forwarded-For $proxy_protocol_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Host $host;
|
||||
|
||||
location / {
|
||||
return 301 https://$host/auth/realms/dev/account;
|
||||
}
|
||||
|
||||
location /auth {
|
||||
proxy_pass ${KEYCLOAK_URI};
|
||||
}
|
||||
|
||||
location /steam {
|
||||
proxy_pass ${STEAMIDP_URI};
|
||||
}
|
||||
}
|
7
docker-compose.debug.yml
Normal file
7
docker-compose.debug.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
version: '2'
|
||||
services:
|
||||
proxy:
|
||||
environment:
|
||||
STEAMIDP_URI: https://steamidp-debugger:5001
|
||||
extra_hosts:
|
||||
- "steamidp-debugger:host-gateway"
|
14
docker-compose.secrets.yml.template
Normal file
14
docker-compose.secrets.yml.template
Normal file
|
@ -0,0 +1,14 @@
|
|||
version: '2'
|
||||
services:
|
||||
proxy:
|
||||
volumes:
|
||||
- "<local-path>/dev.local.crt:/tmp/dev.local.crt"
|
||||
- "<local-path>/dev.local.key:/tmp/dev.local.key"
|
||||
|
||||
keycloak:
|
||||
volumes:
|
||||
- "<local-path>/dev.local.crt:/tmp/dev.local.crt"
|
||||
|
||||
steamidp:
|
||||
environment:
|
||||
Steam__ApplicationKey: <steam-app-key>
|
57
docker-compose.yml
Normal file
57
docker-compose.yml
Normal file
|
@ -0,0 +1,57 @@
|
|||
version: '2'
|
||||
volumes:
|
||||
postgres_data:
|
||||
driver: local
|
||||
|
||||
services:
|
||||
proxy:
|
||||
image: neothor/proxy:develop
|
||||
build: ./develop/proxy
|
||||
container_name: proxy
|
||||
ports:
|
||||
- 443:443
|
||||
links:
|
||||
- keycloak
|
||||
- steamidp
|
||||
|
||||
postgres:
|
||||
image: postgres
|
||||
container_name: postgres
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_DB: keycloak
|
||||
POSTGRES_USER: keycloak
|
||||
POSTGRES_PASSWORD: password
|
||||
|
||||
keycloak:
|
||||
image: neothor/keycloak:develop
|
||||
build: ./develop/keycloak
|
||||
container_name: keycloak
|
||||
environment:
|
||||
DB_VENDOR: POSTGRES
|
||||
DB_ADDR: postgres
|
||||
DB_DATABASE: keycloak
|
||||
DB_USER: keycloak
|
||||
DB_SCHEMA: public
|
||||
DB_PASSWORD: password
|
||||
KEYCLOAK_USER: admin
|
||||
KEYCLOAK_PASSWORD: changeit
|
||||
PROXY_ADDRESS_FORWARDING: "true"
|
||||
links:
|
||||
- postgres
|
||||
extra_hosts:
|
||||
- "dev.local:host-gateway"
|
||||
|
||||
steamidp:
|
||||
image: neothor/steam-openid-connect-provider:develop
|
||||
build: ./src
|
||||
container_name: steamidp
|
||||
links:
|
||||
- keycloak
|
||||
environment:
|
||||
OpenID__ClientID: keycloak
|
||||
OpenID__ClientName: keycloak
|
||||
OpenId__ClientSecret: keycloak
|
||||
OpenID__RedirectUri: https://dev.local/auth/realms/dev/broker/steam/endpoint
|
||||
Hosting__BasePath: /steam
|
|
@ -1,33 +1,48 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using IdentityServer4.Events;
|
||||
using IdentityServer4.Extensions;
|
||||
using IdentityServer4.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SteamOpenIdConnectProvider.Domains.IdentityServer;
|
||||
|
||||
namespace SteamOpenIdConnectProvider.Controllers
|
||||
{
|
||||
[AllowAnonymous]
|
||||
[Route("[action]")]
|
||||
public class ExternalLoginController : Controller
|
||||
{
|
||||
private readonly SignInManager<IdentityUser> _signInManager;
|
||||
private readonly UserManager<IdentityUser> _userManager;
|
||||
private readonly IIdentityServerInteractionService _interaction;
|
||||
private readonly IEventService _events;
|
||||
|
||||
private readonly OpenIdConfig _config;
|
||||
private readonly ILogger<ExternalLoginController> _logger;
|
||||
|
||||
public ExternalLoginController(
|
||||
SignInManager<IdentityUser> signInManager,
|
||||
UserManager<IdentityUser> userManager,
|
||||
IIdentityServerInteractionService interaction,
|
||||
IEventService events,
|
||||
IOptions<OpenIdConfig> config,
|
||||
ILogger<ExternalLoginController> logger)
|
||||
{
|
||||
_signInManager = signInManager;
|
||||
_userManager = userManager;
|
||||
_config = config.Value;
|
||||
_logger = logger;
|
||||
_interaction = interaction;
|
||||
_events = events;
|
||||
}
|
||||
|
||||
|
||||
[HttpGet]
|
||||
[HttpGet("external-login")]
|
||||
public Task<IActionResult> ExternalLogin(string returnUrl = null)
|
||||
{
|
||||
const string provider = "Steam";
|
||||
|
@ -37,11 +52,11 @@ namespace SteamOpenIdConnectProvider.Controllers
|
|||
return Task.FromResult<IActionResult>(new ChallengeResult(provider, properties));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[HttpGet("external-login-callback")]
|
||||
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
|
||||
{
|
||||
returnUrl ??= Url.Content("~/");
|
||||
|
||||
|
||||
if (remoteError != null)
|
||||
{
|
||||
throw new Exception($"Error from external provider: {remoteError}");
|
||||
|
@ -62,7 +77,17 @@ namespace SteamOpenIdConnectProvider.Controllers
|
|||
|
||||
var userName = info.Principal.FindFirstValue(ClaimTypes.Name);
|
||||
var userId = info.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
|
||||
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(userId), $"No claim found for {ClaimTypes.NameIdentifier}");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(userName))
|
||||
{
|
||||
userName = userId.Split('/').Last();
|
||||
}
|
||||
|
||||
var user = new IdentityUser { UserName = userName, Id = userId };
|
||||
|
||||
_userManager.UserValidators.Clear();
|
||||
|
@ -86,5 +111,21 @@ namespace SteamOpenIdConnectProvider.Controllers
|
|||
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
[HttpGet("external-logout")]
|
||||
public async Task<ActionResult> ExternalLogout(string logoutId)
|
||||
{
|
||||
var logout = await _interaction.GetLogoutContextAsync(logoutId);
|
||||
|
||||
if (User?.Identity.IsAuthenticated == true)
|
||||
{
|
||||
await _signInManager.SignOutAsync();
|
||||
await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
|
||||
}
|
||||
|
||||
return Redirect(logout?.PostLogoutRedirectUri ??
|
||||
_config.PostLogoutRedirectUris.FirstOrDefault() ??
|
||||
Url.Content("~/"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
26
src/Dockerfile
Normal file
26
src/Dockerfile
Normal file
|
@ -0,0 +1,26 @@
|
|||
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
# Copy the project file to create layer with packages
|
||||
COPY SteamOpenIdConnectProvider.csproj .
|
||||
RUN dotnet restore ./SteamOpenIdConnectProvider.csproj
|
||||
|
||||
# Copy the rest of the source
|
||||
COPY . .
|
||||
RUN dotnet build ./SteamOpenIdConnectProvider.csproj -c Release -o /app
|
||||
|
||||
FROM build AS publish
|
||||
RUN dotnet publish ./SteamOpenIdConnectProvider.csproj -c Release -o /app
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:5.0
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y curl
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app .
|
||||
EXPOSE 80
|
||||
|
||||
HEALTHCHECK CMD curl --fail http://localhost/health || exit 1
|
||||
ENTRYPOINT ["dotnet", "SteamOpenIdConnectProvider.dll"]
|
16
src/Domains/Common/HostingConfig.cs
Normal file
16
src/Domains/Common/HostingConfig.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SteamOpenIdConnectProvider.Domains.Common
|
||||
{
|
||||
public class HostingConfig
|
||||
{
|
||||
public static readonly string Key = "Hosting";
|
||||
|
||||
public string BasePath { get; set; }
|
||||
|
||||
public string PublicOrigin { get; set; }
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace SteamOpenIdConnectProvider.Database
|
||||
namespace SteamOpenIdConnectProvider.Domains.IdentityServer
|
||||
{
|
||||
// This is completely in-memory, we do not need a persistent store.
|
||||
public class AppInMemoryDbContext : IdentityDbContext<IdentityUser>
|
|
@ -1,33 +1,35 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using IdentityServer4;
|
||||
using IdentityServer4.Models;
|
||||
using SteamOpenIdConnectProvider.Domains.IdentityServer;
|
||||
|
||||
namespace SteamOpenIdConnectProvider
|
||||
namespace SteamOpenIdConnectProvider.Models.IdentityServer
|
||||
{
|
||||
public class IdentityServerConfig
|
||||
public static class IdentityServerConfigFactory
|
||||
{
|
||||
public static IEnumerable<Client> GetClients(string clientId, string secret, string redirectUri, string logoutRedirectUri)
|
||||
public static IEnumerable<Client> GetClients(OpenIdConfig config)
|
||||
{
|
||||
yield return new Client
|
||||
var client = new Client
|
||||
{
|
||||
ClientId = clientId,
|
||||
ClientName = "Proxy Client",
|
||||
ClientId = config.ClientID,
|
||||
ClientName = config.ClientName,
|
||||
AllowedGrantTypes = GrantTypes.Code,
|
||||
RequireConsent = false,
|
||||
ClientSecrets =
|
||||
{
|
||||
new Secret(secret.Sha256())
|
||||
new Secret(config.ClientSecret.Sha256())
|
||||
},
|
||||
AlwaysSendClientClaims = true,
|
||||
AlwaysIncludeUserClaimsInIdToken = true,
|
||||
|
||||
// where to redirect to after login
|
||||
RedirectUris = redirectUri.Split(",").Select(x => x.Trim()).ToArray(),
|
||||
RedirectUris = config.RedirectUris.ToArray(),
|
||||
|
||||
// where to redirect to after logout
|
||||
PostLogoutRedirectUris = { logoutRedirectUri },
|
||||
PostLogoutRedirectUris = config.PostLogoutRedirectUris.ToArray(),
|
||||
|
||||
RequirePkce = false,
|
||||
AllowedScopes = new List<string>
|
||||
{
|
||||
|
@ -35,6 +37,7 @@ namespace SteamOpenIdConnectProvider
|
|||
IdentityServerConstants.StandardScopes.Profile,
|
||||
}
|
||||
};
|
||||
yield return client;
|
||||
}
|
||||
|
||||
public static IEnumerable<IdentityResource> GetIdentityResources()
|
||||
|
@ -46,4 +49,4 @@ namespace SteamOpenIdConnectProvider
|
|||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
src/Domains/IdentityServer/OpenIdConfig.cs
Normal file
26
src/Domains/IdentityServer/OpenIdConfig.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SteamOpenIdConnectProvider.Domains.IdentityServer
|
||||
{
|
||||
public class OpenIdConfig
|
||||
{
|
||||
public static readonly string Key = "OpenID";
|
||||
|
||||
public string ClientID { get; set; }
|
||||
public string ClientSecret { get; set; }
|
||||
public string RedirectUri { get; set; }
|
||||
public string PostLogoutRedirectUri { get; set; }
|
||||
public string ClientName { get; set; } = "Proxy Client";
|
||||
|
||||
public IEnumerable<string> RedirectUris => (RedirectUri ?? string.Empty).Split(
|
||||
new[] { ',', ';' },
|
||||
StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
public IEnumerable<string> PostLogoutRedirectUris => (PostLogoutRedirectUri ?? string.Empty).Split(
|
||||
new[] { ',', ';' },
|
||||
StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
}
|
30
src/Domains/IdentityServer/OpenIdStandardClaims.cs
Normal file
30
src/Domains/IdentityServer/OpenIdStandardClaims.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SteamOpenIdConnectProvider.Domains.IdentityServer
|
||||
{
|
||||
public static class OpenIdStandardClaims
|
||||
{
|
||||
public static readonly string Name = "name";
|
||||
public static readonly string GivenName = "given_name";
|
||||
public static readonly string FamilyName = "family_name";
|
||||
public static readonly string MiddleName = "middle_name";
|
||||
public static readonly string Nickname = "nickname";
|
||||
public static readonly string PreferredUsername = "preferred_username";
|
||||
public static readonly string Profile = "profile";
|
||||
public static readonly string Picture = "picture";
|
||||
public static readonly string Website = "website";
|
||||
public static readonly string Email = "email";
|
||||
public static readonly string EmailVerified = "email_verified";
|
||||
public static readonly string Gender = "gender";
|
||||
public static readonly string BirthDate = "BirthDate";
|
||||
public static readonly string Zoneinfo = "zoneinfo";
|
||||
public static readonly string Locale = "locale";
|
||||
public static readonly string PhoneNumber = "phone_number";
|
||||
public static readonly string PhoneNumberVerified = "phone_number_verified";
|
||||
public static readonly string Address = "address";
|
||||
public static readonly string UpdatedAt = "updated_at";
|
||||
}
|
||||
}
|
7
src/Domains/IdentityServer/SteamClaims.cs
Normal file
7
src/Domains/IdentityServer/SteamClaims.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace SteamOpenIdConnectProvider.Domains.IdentityServer
|
||||
{
|
||||
public static class SteamClaims
|
||||
{
|
||||
public static readonly string SteamId = "steam_id";
|
||||
}
|
||||
}
|
14
src/Domains/Steam/Constants.cs
Normal file
14
src/Domains/Steam/Constants.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SteamOpenIdConnectProvider.Domains.Steam
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
public static readonly string OpenIdUrl = "https://steamcommunity.com/openid/id/";
|
||||
public static readonly string ApiUrl = "https://api.steampowered.com/";
|
||||
public static readonly string GetPlayerSummariesUrl = ApiUrl + "ISteamUser/GetPlayerSummaries/v0002";
|
||||
}
|
||||
}
|
12
src/Domains/Steam/GetPlayerSummariesResponse.cs
Normal file
12
src/Domains/Steam/GetPlayerSummariesResponse.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using SteamOpenIdConnectProvider.Profile.Models;
|
||||
|
||||
namespace SteamOpenIdConnectProvider.Domains.Steam
|
||||
{
|
||||
public sealed class GetPlayerSummariesResponse
|
||||
{
|
||||
[JsonPropertyName("players")]
|
||||
public ICollection<Player> Players { get; set; }
|
||||
}
|
||||
}
|
59
src/Domains/Steam/Player.cs
Normal file
59
src/Domains/Steam/Player.cs
Normal file
|
@ -0,0 +1,59 @@
|
|||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SteamOpenIdConnectProvider.Profile.Models
|
||||
{
|
||||
public sealed class Player
|
||||
{
|
||||
[JsonPropertyName("steamid")]
|
||||
public string SteamId { get; set; }
|
||||
|
||||
[JsonPropertyName("communityvisibilitystate")]
|
||||
public int CommunityVisibilityState { get; set; }
|
||||
|
||||
[JsonPropertyName("profilestate")]
|
||||
public int ProfileState { get; set; }
|
||||
|
||||
[JsonPropertyName("personaname")]
|
||||
public string PersonaName { get; set; }
|
||||
|
||||
[JsonPropertyName("commentpermission")]
|
||||
public int CommentPermission { get; set; }
|
||||
|
||||
[JsonPropertyName("profileurl")]
|
||||
public string ProfileUrl { get; set; }
|
||||
|
||||
[JsonPropertyName("avatar")]
|
||||
public string Avatar { get; set; }
|
||||
|
||||
[JsonPropertyName("avatarmedium")]
|
||||
public string AvatarMedium { get; set; }
|
||||
|
||||
[JsonPropertyName("avatarfull")]
|
||||
public string AvatarFull { get; set; }
|
||||
|
||||
[JsonPropertyName("avatarhash")]
|
||||
public string AvatarHash { get; set; }
|
||||
|
||||
[JsonPropertyName("lastlogoff")]
|
||||
public int LastLogoff { get; set; }
|
||||
|
||||
[JsonPropertyName("personastate")]
|
||||
public int PersonaState { get; set; }
|
||||
|
||||
[JsonPropertyName("realname")]
|
||||
public string RealName { get; set; }
|
||||
|
||||
[JsonPropertyName("primaryclanid")]
|
||||
public string PrimaryClanId { get; set; }
|
||||
|
||||
[JsonPropertyName("timecreated")]
|
||||
public int TimeCreated { get; set; }
|
||||
|
||||
[JsonPropertyName("personastateflags")]
|
||||
public int PersonaStateFlags { get; set; }
|
||||
|
||||
[JsonPropertyName("loccountrycode")]
|
||||
public string LocCountryCode { get; set; }
|
||||
}
|
||||
}
|
14
src/Domains/Steam/SteamConfig.cs
Normal file
14
src/Domains/Steam/SteamConfig.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SteamOpenIdConnectProvider.Domains.Steam
|
||||
{
|
||||
public class SteamConfig
|
||||
{
|
||||
public static readonly string Key = "Steam";
|
||||
|
||||
public string ApplicationKey { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,46 +1,40 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using IdentityServer4.Extensions;
|
||||
using IdentityServer4.Models;
|
||||
using IdentityServer4.Services;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Newtonsoft.Json;
|
||||
using SteamOpenIdConnectProvider.Profile.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SteamOpenIdConnectProvider.Domains.IdentityServer;
|
||||
using SteamOpenIdConnectProvider.Domains.Steam;
|
||||
using SteamOpenIdConnectProvider.Models.Steam;
|
||||
|
||||
namespace SteamOpenIdConnectProvider.Profile
|
||||
namespace SteamOpenIdConnectProvider.Services
|
||||
{
|
||||
public class SteamProfileService : IProfileService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly SteamConfig _config;
|
||||
private readonly IUserClaimsPrincipalFactory<IdentityUser> _claimsFactory;
|
||||
private readonly ILogger<SteamProfileService> _logger;
|
||||
private readonly UserManager<IdentityUser> _userManager;
|
||||
|
||||
private async Task<GetPlayerSummariesResponse> GetPlayerSummariesAsync(IEnumerable<string> steamIds)
|
||||
{
|
||||
const string baseurl = "https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002";
|
||||
|
||||
var applicationKey = _configuration["Authentication:Steam:ApplicationKey"];
|
||||
var url = $"{baseurl}/?key={applicationKey}&steamids={string.Join(',', steamIds)}";
|
||||
|
||||
var res = await _httpClient.GetStringAsync(url);
|
||||
var response = JsonConvert.DeserializeObject<SteamResponse<GetPlayerSummariesResponse>>(res);
|
||||
return response.Response;
|
||||
}
|
||||
|
||||
public SteamProfileService(
|
||||
UserManager<IdentityUser> userManager,
|
||||
IUserClaimsPrincipalFactory<IdentityUser> claimsFactory,
|
||||
IConfiguration configuration,
|
||||
IOptions<SteamConfig> config,
|
||||
ILogger<SteamProfileService> logger,
|
||||
HttpClient httpClient)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_claimsFactory = claimsFactory;
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
_config = config.Value;
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
|
@ -53,23 +47,42 @@ namespace SteamOpenIdConnectProvider.Profile
|
|||
var claims = principal.Claims.ToList();
|
||||
claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList();
|
||||
|
||||
const string steamUrl = "https://steamcommunity.com/openid/id/";
|
||||
var steamId = sub.Substring(steamUrl.Length);
|
||||
var steamId = sub.Substring(Constants.OpenIdUrl.Length);
|
||||
AddClaim(claims, SteamClaims.SteamId, steamId);
|
||||
|
||||
var userSummary = await GetPlayerSummariesAsync(new[] { steamId });
|
||||
var player = userSummary.Players.FirstOrDefault();
|
||||
|
||||
if (player != null)
|
||||
{
|
||||
AddClaim(claims, "picture", player.AvatarFull);
|
||||
AddClaim(claims, "nickname", player.PersonaName);
|
||||
AddClaim(claims, "given_name", player.RealName);
|
||||
AddClaim(claims, "website", player.ProfileUrl);
|
||||
AddClaim(claims, OpenIdStandardClaims.Picture, player.AvatarFull);
|
||||
AddClaim(claims, OpenIdStandardClaims.Nickname, player.PersonaName);
|
||||
AddClaim(claims, OpenIdStandardClaims.PreferredUsername, player.PersonaName);
|
||||
AddClaim(claims, OpenIdStandardClaims.GivenName, player.RealName);
|
||||
AddClaim(claims, OpenIdStandardClaims.Website, player.ProfileUrl);
|
||||
}
|
||||
|
||||
if (_logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
foreach (var claim in claims)
|
||||
{
|
||||
_logger.LogDebug("Issued claim {claim}:{value} for {principle}",
|
||||
claim.Type,
|
||||
claim.Value,
|
||||
principal.Identity.Name);
|
||||
}
|
||||
}
|
||||
|
||||
context.IssuedClaims = claims;
|
||||
}
|
||||
|
||||
public async Task IsActiveAsync(IsActiveContext context)
|
||||
{
|
||||
var sub = context.Subject.GetSubjectId();
|
||||
var user = await _userManager.FindByIdAsync(sub);
|
||||
context.IsActive = user != null;
|
||||
}
|
||||
|
||||
private void AddClaim(List<Claim> claims, string type, string value)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
|
@ -78,11 +91,12 @@ namespace SteamOpenIdConnectProvider.Profile
|
|||
}
|
||||
}
|
||||
|
||||
public async Task IsActiveAsync(IsActiveContext context)
|
||||
private async Task<GetPlayerSummariesResponse> GetPlayerSummariesAsync(IEnumerable<string> steamIds)
|
||||
{
|
||||
var sub = context.Subject.GetSubjectId();
|
||||
var user = await _userManager.FindByIdAsync(sub);
|
||||
context.IsActive = user != null;
|
||||
var url = $"{Constants.GetPlayerSummariesUrl}/?key={_config.ApplicationKey}&steamids={string.Join(',', steamIds)}";
|
||||
var res = await _httpClient.GetStringAsync(url);
|
||||
var response = JsonSerializer.Deserialize<SteamResponse<GetPlayerSummariesResponse>>(res);
|
||||
return response.Response;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
src/Domains/Steam/SteamResponse.cs
Normal file
10
src/Domains/Steam/SteamResponse.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SteamOpenIdConnectProvider.Models.Steam
|
||||
{
|
||||
public sealed class SteamResponse<T>
|
||||
{
|
||||
[JsonPropertyName("response")]
|
||||
public T Response { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace SteamOpenIdConnectProvider.Profile.Models
|
||||
{
|
||||
public sealed class GetPlayerSummariesResponse
|
||||
{
|
||||
[JsonProperty("players")]
|
||||
public ICollection<Player> Players { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace SteamOpenIdConnectProvider.Profile.Models
|
||||
{
|
||||
public sealed class Player
|
||||
{
|
||||
[JsonProperty("steamid")]
|
||||
public ulong SteamId { get; set; }
|
||||
|
||||
[JsonProperty("communityvisibilitystate")]
|
||||
public int CommunityVisibilityState { get; set; }
|
||||
|
||||
[JsonProperty("profilestate")]
|
||||
public int ProfileState { get; set; }
|
||||
|
||||
[JsonProperty("personaname")]
|
||||
public string PersonaName { get; set; }
|
||||
|
||||
[JsonProperty("commentpermission")]
|
||||
public int CommentPermission { get; set; }
|
||||
|
||||
[JsonProperty("profileurl")]
|
||||
public string ProfileUrl { get; set; }
|
||||
|
||||
[JsonProperty("avatar")]
|
||||
public string Avatar { get; set; }
|
||||
|
||||
[JsonProperty("avatarmedium")]
|
||||
public string AvatarMedium { get; set; }
|
||||
|
||||
[JsonProperty("avatarfull")]
|
||||
public string AvatarFull { get; set; }
|
||||
|
||||
[JsonProperty("avatarhash")]
|
||||
public string AvatarHash { get; set; }
|
||||
|
||||
[JsonProperty("lastlogoff")]
|
||||
public int LastLogoff { get; set; }
|
||||
|
||||
[JsonProperty("personastate")]
|
||||
public int PersonaState { get; set; }
|
||||
|
||||
[JsonProperty("realname")]
|
||||
public string RealName { get; set; }
|
||||
|
||||
[JsonProperty("primaryclanid")]
|
||||
public ulong PrimaryClanId { get; set; }
|
||||
|
||||
[JsonProperty("timecreated")]
|
||||
public int TimeCreated { get; set; }
|
||||
|
||||
[JsonProperty("personastateflags")]
|
||||
public int PersonaStateFlags { get; set; }
|
||||
|
||||
[JsonProperty("loccountrycode")]
|
||||
public string LocCountryCode { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace SteamOpenIdConnectProvider.Profile.Models
|
||||
{
|
||||
public sealed class SteamResponse<T>
|
||||
{
|
||||
[JsonProperty("response")]
|
||||
public T Response { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
using Microsoft.AspNetCore;
|
||||
using System;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Serilog.Sinks.SystemConsole.Themes;
|
||||
|
@ -8,24 +10,42 @@ namespace SteamOpenIdConnectProvider
|
|||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("System", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information)
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Literate)
|
||||
.CreateLogger();
|
||||
.WriteTo.Console()
|
||||
.CreateBootstrapLogger();
|
||||
|
||||
CreateWebHostBuilder(args).Build().Run();
|
||||
try
|
||||
{
|
||||
Log.Information("Starting web host");
|
||||
CreateHostBuilder(args).Build().Run();
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Fatal(ex, "Host terminated unexpectedly");
|
||||
return 1;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
}
|
||||
}
|
||||
|
||||
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseKestrel()
|
||||
.UseSerilog()
|
||||
.UseStartup<Startup>();
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.UseSerilog((context, services, configuration) => configuration
|
||||
.ReadFrom.Configuration(context.Configuration)
|
||||
.ReadFrom.Services(services)
|
||||
.MinimumLevel.Is(context.HostingEnvironment.IsDevelopment() ? LogEventLevel.Debug : LogEventLevel.Information)
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console(theme: AnsiConsoleTheme.Code))
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Net.Http;
|
||||
using IdentityServer4.Extensions;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
@ -12,8 +11,15 @@ using Microsoft.Extensions.DependencyInjection;
|
|||
using Microsoft.Extensions.Hosting;
|
||||
using IdentityServer4.Services;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using SteamOpenIdConnectProvider.Database;
|
||||
using SteamOpenIdConnectProvider.Profile;
|
||||
using SteamOpenIdConnectProvider.Services;
|
||||
using SteamOpenIdConnectProvider.Models.IdentityServer;
|
||||
using SteamOpenIdConnectProvider.Domains.Common;
|
||||
using SteamOpenIdConnectProvider.Domains.IdentityServer;
|
||||
using SteamOpenIdConnectProvider.Domains.Steam;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Serilog;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace SteamOpenIdConnectProvider
|
||||
{
|
||||
|
@ -29,10 +35,8 @@ namespace SteamOpenIdConnectProvider
|
|||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddControllers()
|
||||
.AddNewtonsoftJson()
|
||||
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
|
||||
|
||||
services.AddSingleton(Configuration);
|
||||
services.AddDbContext<AppInMemoryDbContext>(options =>
|
||||
options.UseInMemoryDatabase("default"));
|
||||
|
||||
|
@ -43,21 +47,24 @@ namespace SteamOpenIdConnectProvider
|
|||
.AddEntityFrameworkStores<AppInMemoryDbContext>()
|
||||
.AddDefaultTokenProviders();
|
||||
|
||||
services.AddIdentityServer(options =>
|
||||
var openIdConfig = Configuration.GetSection(OpenIdConfig.Key);
|
||||
services
|
||||
.Configure<OpenIdConfig>(openIdConfig)
|
||||
.AddIdentityServer(options =>
|
||||
{
|
||||
options.UserInteraction.LoginUrl = "/ExternalLogin";
|
||||
options.UserInteraction.LoginUrl = "/external-login";
|
||||
options.UserInteraction.LogoutUrl = "/external-logout";
|
||||
})
|
||||
.AddAspNetIdentity<IdentityUser>()
|
||||
.AddInMemoryClients(IdentityServerConfig.GetClients(
|
||||
Configuration["OpenID:ClientID"],
|
||||
Configuration["OpenID:ClientSecret"],
|
||||
Configuration["OpenID:RedirectUri"],
|
||||
Configuration["OpenID:PostLogoutRedirectUri"]))
|
||||
.AddInMemoryClients(IdentityServerConfigFactory.GetClients(openIdConfig.Get<OpenIdConfig>()))
|
||||
.AddInMemoryPersistedGrants()
|
||||
.AddDeveloperSigningCredential(true)
|
||||
.AddInMemoryIdentityResources(IdentityServerConfig.GetIdentityResources());
|
||||
.AddInMemoryIdentityResources(IdentityServerConfigFactory.GetIdentityResources());
|
||||
|
||||
services.AddHttpClient<IProfileService, SteamProfileService>();
|
||||
var steamConfig = Configuration.GetSection(SteamConfig.Key);
|
||||
services
|
||||
.Configure<SteamConfig>(steamConfig)
|
||||
.AddHttpClient<IProfileService, SteamProfileService>();
|
||||
|
||||
services.AddAuthentication()
|
||||
.AddCookie(options =>
|
||||
|
@ -67,32 +74,37 @@ namespace SteamOpenIdConnectProvider
|
|||
})
|
||||
.AddSteam(options =>
|
||||
{
|
||||
options.ApplicationKey = Configuration["Authentication:Steam:ApplicationKey"];
|
||||
options.ApplicationKey = steamConfig.Get<SteamConfig>().ApplicationKey;
|
||||
});
|
||||
|
||||
services.AddHealthChecks()
|
||||
.AddUrlGroup(new Uri("https://steamcommunity.com/openid"), "Steam");
|
||||
.AddUrlGroup(new Uri(Constants.OpenIdUrl), "Steam");
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
var logger = app.ApplicationServices.GetRequiredService<ILogger<Startup>>();
|
||||
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
logger.LogWarning("Starting up in development mode");
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Configuration["Hosting:PathBase"]))
|
||||
var hostingConfig = Configuration.GetSection(HostingConfig.Key).Get<HostingConfig>();
|
||||
if (!string.IsNullOrEmpty(hostingConfig.BasePath))
|
||||
{
|
||||
app.UsePathBase(Configuration["Hosting:PathBase"]);
|
||||
app.UsePathBase(hostingConfig.BasePath);
|
||||
}
|
||||
|
||||
app.UseSerilogRequestLogging();
|
||||
|
||||
app.UseCookiePolicy();
|
||||
app.Use(async (ctx, next) =>
|
||||
{
|
||||
var origin = Configuration["Hosting:PublicOrigin"];
|
||||
if (!string.IsNullOrEmpty(origin))
|
||||
if (!string.IsNullOrEmpty(hostingConfig.PublicOrigin))
|
||||
{
|
||||
ctx.SetIdentityServerOrigin(origin);
|
||||
ctx.SetIdentityServerOrigin(hostingConfig.PublicOrigin);
|
||||
}
|
||||
|
||||
await next();
|
||||
|
|
|
@ -1,29 +1,25 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
|
||||
<UserSecretsId>9f8cb9ce-f696-422f-901a-563af9413684</UserSecretsId>
|
||||
<LangVersion>8</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AspNet.Security.OpenId.Steam" Version="3.1.0" />
|
||||
<PackageReference Include="AspNet.Security.OpenIdConnect.Server" Version="2.0.0" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.Uris" Version="3.1.2" />
|
||||
<PackageReference Include="IdentityServer4" Version="4.1.1" />
|
||||
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="4.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="AspNet.Security.OpenId.Steam" Version="5.0.0" />
|
||||
<PackageReference Include="AspNetCore.HealthChecks.Uris" Version="5.0.1" />
|
||||
<PackageReference Include="IdentityServer4" Version="4.1.2" />
|
||||
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="4.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.8" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="5.0.5" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="5.0.2" />
|
||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.28803.202
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SteamOpenIdConnectProvider", "src\SteamOpenIdConnectProvider.csproj", "{39261E1A-B9AB-45C1-991E-B88ED3E076A2}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SteamOpenIdConnectProvider", "SteamOpenIdConnectProvider.csproj", "{39261E1A-B9AB-45C1-991E-B88ED3E076A2}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
@ -1,12 +1,5 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
},
|
||||
"Hosting": {
|
||||
"PathBase": "/test"
|
||||
"BasePath": "/steam"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
},
|
||||
"OpenID": {
|
||||
"ClientID": "proxy",
|
||||
"ClientSecret": "secret",
|
||||
"RedirectUri": "http://localhost:8080/auth/realms/master/broker/steam/endpoint",
|
||||
"PostLogoutRedirectUri": ""
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"OpenID": {
|
||||
"ClientID": "keycloak",
|
||||
"ClientSecret": "keycloak",
|
||||
"ClientName": "keycloak",
|
||||
"RedirectUri": "https://dev.local/auth/realms/dev/broker/steam/endpoint",
|
||||
|
||||
// TODO: Don't think this is how it suppose to work.
|
||||
"PostLogoutRedirectUri": "https://dev.local/auth/realms/dev/protocol/openid-connect/logout?initiating_idp=steam"
|
||||
},
|
||||
"Hosting": {
|
||||
"PathBase": ""
|
||||
"BasePath": ""
|
||||
},
|
||||
"Steam": {
|
||||
"ApplicationKey": "secret"
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue